Consolidate authentication methods (#3988)
Co-authored-by: Micha de Vries <micha@devrie.sh>
This commit is contained in:
parent
e37a6fb18b
commit
e38b891e62
93 changed files with 3581 additions and 2214 deletions
|
@ -21,7 +21,7 @@ pub static MAX_COMPUTATION_DEPTH: Lazy<u32> =
|
||||||
lazy_env_parse!("SURREAL_MAX_COMPUTATION_DEPTH", u32, 120);
|
lazy_env_parse!("SURREAL_MAX_COMPUTATION_DEPTH", u32, 120);
|
||||||
|
|
||||||
/// Specifies the names of parameters which can not be specified in a query.
|
/// Specifies the names of parameters which can not be specified in a query.
|
||||||
pub const PROTECTED_PARAM_NAMES: &[&str] = &["auth", "scope", "token", "session"];
|
pub const PROTECTED_PARAM_NAMES: &[&str] = &["access", "auth", "token", "session"];
|
||||||
|
|
||||||
/// The characters which are supported in server record IDs.
|
/// The characters which are supported in server record IDs.
|
||||||
pub const ID_CHARS: [char; 36] = [
|
pub const ID_CHARS: [char; 36] = [
|
||||||
|
@ -35,9 +35,9 @@ pub const SERVER_NAME: &str = "SurrealDB";
|
||||||
/// Datastore processor batch size for scan operations
|
/// Datastore processor batch size for scan operations
|
||||||
pub const PROCESSOR_BATCH_SIZE: u32 = 50;
|
pub const PROCESSOR_BATCH_SIZE: u32 = 50;
|
||||||
|
|
||||||
/// Forward all signup/signin query errors to a client trying authenticate to a scope. Do not use in production.
|
/// Forward all signup/signin query errors to a client performing record access. Do not use in production.
|
||||||
pub static INSECURE_FORWARD_SCOPE_ERRORS: Lazy<bool> =
|
pub static INSECURE_FORWARD_RECORD_ACCESS_ERRORS: Lazy<bool> =
|
||||||
lazy_env_parse!("SURREAL_INSECURE_FORWARD_SCOPE_ERRORS", bool, false);
|
lazy_env_parse!("SURREAL_INSECURE_FORWARD_RECORD_ACCESS_ERRORS", bool, false);
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
feature = "kv-surrealkv",
|
feature = "kv-surrealkv",
|
||||||
|
|
|
@ -405,9 +405,10 @@ impl Options {
|
||||||
self.valid_for_db()?;
|
self.valid_for_db()?;
|
||||||
res.on_db(self.ns(), self.db())
|
res.on_db(self.ns(), self.db())
|
||||||
}
|
}
|
||||||
Base::Sc(sc) => {
|
// TODO(gguillemas): This variant is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||||
self.valid_for_db()?;
|
Base::Sc(_) => {
|
||||||
res.on_scope(self.ns(), self.db(), sc)
|
// We should not get here, the scope base is only used in parsing for backward compatibility.
|
||||||
|
return Err(Error::InvalidAuth);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,12 @@ pub struct Session {
|
||||||
pub ns: Option<String>,
|
pub ns: Option<String>,
|
||||||
/// The currently selected database
|
/// The currently selected database
|
||||||
pub db: Option<String>,
|
pub db: Option<String>,
|
||||||
/// The currently selected authentication scope
|
/// The current access method
|
||||||
pub sc: Option<String>,
|
pub ac: Option<String>,
|
||||||
/// The current scope authentication token
|
/// The current authentication token
|
||||||
pub tk: Option<Value>,
|
pub tk: Option<Value>,
|
||||||
/// The current scope authentication data
|
/// The current record authentication data
|
||||||
pub sd: Option<Value>,
|
pub rd: Option<Value>,
|
||||||
/// The current expiration time of the session
|
/// The current expiration time of the session
|
||||||
pub exp: Option<i64>,
|
pub exp: Option<i64>,
|
||||||
}
|
}
|
||||||
|
@ -46,9 +46,9 @@ impl Session {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the selected database for the session
|
/// Set the selected access method for the session
|
||||||
pub fn with_sc(mut self, sc: &str) -> Session {
|
pub fn with_ac(mut self, ac: &str) -> Session {
|
||||||
self.sc = Some(sc.to_owned());
|
self.ac = Some(ac.to_owned());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,26 +84,26 @@ impl Session {
|
||||||
|
|
||||||
/// Convert a session into a runtime
|
/// Convert a session into a runtime
|
||||||
pub(crate) fn context<'a>(&self, mut ctx: Context<'a>) -> Context<'a> {
|
pub(crate) fn context<'a>(&self, mut ctx: Context<'a>) -> Context<'a> {
|
||||||
// Add scope auth data
|
// Add access method data
|
||||||
let val: Value = self.sd.to_owned().into();
|
let val: Value = self.ac.to_owned().into();
|
||||||
|
ctx.add_value("access", val);
|
||||||
|
// Add record access data
|
||||||
|
let val: Value = self.rd.to_owned().into();
|
||||||
ctx.add_value("auth", val);
|
ctx.add_value("auth", val);
|
||||||
// Add scope data
|
|
||||||
let val: Value = self.sc.to_owned().into();
|
|
||||||
ctx.add_value("scope", val);
|
|
||||||
// Add token data
|
// Add token data
|
||||||
let val: Value = self.tk.to_owned().into();
|
let val: Value = self.tk.to_owned().into();
|
||||||
ctx.add_value("token", val);
|
ctx.add_value("token", val);
|
||||||
// Add session value
|
// Add session value
|
||||||
let val: Value = Value::from(map! {
|
let val: Value = Value::from(map! {
|
||||||
|
"ac".to_string() => self.ac.to_owned().into(),
|
||||||
|
"exp".to_string() => self.exp.to_owned().into(),
|
||||||
"db".to_string() => self.db.to_owned().into(),
|
"db".to_string() => self.db.to_owned().into(),
|
||||||
"id".to_string() => self.id.to_owned().into(),
|
"id".to_string() => self.id.to_owned().into(),
|
||||||
"ip".to_string() => self.ip.to_owned().into(),
|
"ip".to_string() => self.ip.to_owned().into(),
|
||||||
"ns".to_string() => self.ns.to_owned().into(),
|
"ns".to_string() => self.ns.to_owned().into(),
|
||||||
"or".to_string() => self.or.to_owned().into(),
|
"or".to_string() => self.or.to_owned().into(),
|
||||||
"sc".to_string() => self.sc.to_owned().into(),
|
"rd".to_string() => self.rd.to_owned().into(),
|
||||||
"sd".to_string() => self.sd.to_owned().into(),
|
|
||||||
"tk".to_string() => self.tk.to_owned().into(),
|
"tk".to_string() => self.tk.to_owned().into(),
|
||||||
"exp".to_string() => self.exp.to_owned().into(),
|
|
||||||
});
|
});
|
||||||
ctx.add_value("session", val);
|
ctx.add_value("session", val);
|
||||||
// Output context
|
// Output context
|
||||||
|
@ -133,19 +133,19 @@ impl Session {
|
||||||
sess
|
sess
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a scoped session for a given NS and DB
|
/// Create a record user session for a given NS and DB
|
||||||
pub fn for_scope(ns: &str, db: &str, sc: &str, rid: Value) -> Session {
|
pub fn for_record(ns: &str, db: &str, ac: &str, rid: Value) -> Session {
|
||||||
Session {
|
Session {
|
||||||
au: Arc::new(Auth::for_sc(rid.to_string(), ns, db, sc)),
|
ac: Some(ac.to_owned()),
|
||||||
|
au: Arc::new(Auth::for_record(rid.to_string(), ns, db, ac)),
|
||||||
rt: false,
|
rt: false,
|
||||||
ip: None,
|
ip: None,
|
||||||
or: None,
|
or: None,
|
||||||
id: None,
|
id: None,
|
||||||
ns: Some(ns.to_owned()),
|
ns: Some(ns.to_owned()),
|
||||||
db: Some(db.to_owned()),
|
db: Some(db.to_owned()),
|
||||||
sc: Some(sc.to_owned()),
|
|
||||||
tk: None,
|
tk: None,
|
||||||
sd: Some(rid),
|
rd: Some(rid),
|
||||||
exp: None,
|
exp: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ use crate::doc::CursorDoc;
|
||||||
use crate::doc::Document;
|
use crate::doc::Document;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::fflags::FFLAGS;
|
use crate::fflags::FFLAGS;
|
||||||
|
use crate::sql::paths::AC;
|
||||||
use crate::sql::paths::META;
|
use crate::sql::paths::META;
|
||||||
use crate::sql::paths::SC;
|
use crate::sql::paths::RD;
|
||||||
use crate::sql::paths::SD;
|
|
||||||
use crate::sql::paths::TK;
|
use crate::sql::paths::TK;
|
||||||
use crate::sql::permission::Permission;
|
use crate::sql::permission::Permission;
|
||||||
use crate::sql::statements::LiveStatement;
|
use crate::sql::statements::LiveStatement;
|
||||||
|
@ -161,8 +161,8 @@ impl<'a> Document<'a> {
|
||||||
// This ensures that we are using the session
|
// This ensures that we are using the session
|
||||||
// of the user who created the LIVE query.
|
// of the user who created the LIVE query.
|
||||||
let mut lqctx = Context::background();
|
let mut lqctx = Context::background();
|
||||||
lqctx.add_value("auth", sess.pick(SD.as_ref()));
|
lqctx.add_value("access", sess.pick(AC.as_ref()));
|
||||||
lqctx.add_value("scope", sess.pick(SC.as_ref()));
|
lqctx.add_value("auth", sess.pick(RD.as_ref()));
|
||||||
lqctx.add_value("token", sess.pick(TK.as_ref()));
|
lqctx.add_value("token", sess.pick(TK.as_ref()));
|
||||||
lqctx.add_value("session", sess);
|
lqctx.add_value("session", sess);
|
||||||
// We need to create a new options which we will
|
// We need to create a new options which we will
|
||||||
|
|
|
@ -299,9 +299,9 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested namespace token does not exist
|
/// The requested namespace access method does not exist
|
||||||
#[error("The namespace token '{value}' does not exist")]
|
#[error("The namespace access method '{value}' does not exist")]
|
||||||
NtNotFound {
|
NaNotFound {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -317,9 +317,9 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested database token does not exist
|
/// The requested database access method does not exist
|
||||||
#[error("The database token '{value}' does not exist")]
|
#[error("The database access method '{value}' does not exist")]
|
||||||
DtNotFound {
|
DaNotFound {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -353,12 +353,6 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested scope does not exist
|
|
||||||
#[error("The scope '{value}' does not exist")]
|
|
||||||
ScNotFound {
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
// The cluster node already exists
|
// The cluster node already exists
|
||||||
#[error("The node '{value}' already exists")]
|
#[error("The node '{value}' already exists")]
|
||||||
ClAlreadyExists {
|
ClAlreadyExists {
|
||||||
|
@ -371,12 +365,6 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested scope token does not exist
|
|
||||||
#[error("The scope token '{value}' does not exist")]
|
|
||||||
StNotFound {
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The requested param does not exist
|
/// The requested param does not exist
|
||||||
#[error("The param '${value}' does not exist")]
|
#[error("The param '${value}' does not exist")]
|
||||||
PaNotFound {
|
PaNotFound {
|
||||||
|
@ -764,15 +752,6 @@ pub enum Error {
|
||||||
#[error("The signin query failed")]
|
#[error("The signin query failed")]
|
||||||
SigninQueryFailed,
|
SigninQueryFailed,
|
||||||
|
|
||||||
#[error("This scope does not allow signup")]
|
|
||||||
ScopeNoSignup,
|
|
||||||
|
|
||||||
#[error("This scope does not allow signin")]
|
|
||||||
ScopeNoSignin,
|
|
||||||
|
|
||||||
#[error("The scope does not exist")]
|
|
||||||
NoScopeFound,
|
|
||||||
|
|
||||||
#[error("Username or Password was not provided")]
|
#[error("Username or Password was not provided")]
|
||||||
MissingUserOrPass,
|
MissingUserOrPass,
|
||||||
|
|
||||||
|
@ -864,12 +843,6 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested scope already exists
|
|
||||||
#[error("The scope '{value}' already exists")]
|
|
||||||
ScAlreadyExists {
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The requested table already exists
|
/// The requested table already exists
|
||||||
#[error("The table '{value}' already exists")]
|
#[error("The table '{value}' already exists")]
|
||||||
TbAlreadyExists {
|
TbAlreadyExists {
|
||||||
|
@ -888,12 +861,6 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested scope token already exists
|
|
||||||
#[error("The scope token '{value}' already exists")]
|
|
||||||
StAlreadyExists {
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The requested user already exists
|
/// The requested user already exists
|
||||||
#[error("The user '{value}' already exists")]
|
#[error("The user '{value}' already exists")]
|
||||||
UserRootAlreadyExists {
|
UserRootAlreadyExists {
|
||||||
|
@ -921,14 +888,6 @@ pub enum Error {
|
||||||
#[error("The session has expired")]
|
#[error("The session has expired")]
|
||||||
ExpiredSession,
|
ExpiredSession,
|
||||||
|
|
||||||
/// The session has an invalid duration
|
|
||||||
#[error("The session has an invalid duration")]
|
|
||||||
InvalidSessionDuration,
|
|
||||||
|
|
||||||
/// The session has an invalid expiration
|
|
||||||
#[error("The session has an invalid expiration")]
|
|
||||||
InvalidSessionExpiration,
|
|
||||||
|
|
||||||
/// A node task has failed
|
/// A node task has failed
|
||||||
#[error("A node task has failed: {0}")]
|
#[error("A node task has failed: {0}")]
|
||||||
NodeAgent(&'static str),
|
NodeAgent(&'static str),
|
||||||
|
@ -940,6 +899,70 @@ pub enum Error {
|
||||||
/// The supplied type could not be serialiazed into `sql::Value`
|
/// The supplied type could not be serialiazed into `sql::Value`
|
||||||
#[error("Serialization error: {0}")]
|
#[error("Serialization error: {0}")]
|
||||||
Serialization(String),
|
Serialization(String),
|
||||||
|
|
||||||
|
/// The requested root access method already exists
|
||||||
|
#[error("The root access method '{value}' already exists")]
|
||||||
|
AccessRootAlreadyExists {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The requested namespace access method already exists
|
||||||
|
#[error("The namespace access method '{value}' already exists")]
|
||||||
|
AccessNsAlreadyExists {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The requested database access method already exists
|
||||||
|
#[error("The database access method '{value}' already exists")]
|
||||||
|
AccessDbAlreadyExists {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The requested root access method does not exist
|
||||||
|
#[error("The root access method '{value}' does not exist")]
|
||||||
|
AccessRootNotFound {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The requested namespace access method does not exist
|
||||||
|
#[error("The namespace access method '{value}' does not exist")]
|
||||||
|
AccessNsNotFound {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The requested database access method does not exist
|
||||||
|
#[error("The database access method '{value}' does not exist")]
|
||||||
|
AccessDbNotFound {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The access method cannot be defined on the requested level
|
||||||
|
#[error("The access method cannot be defined on the requested level")]
|
||||||
|
AccessLevelMismatch,
|
||||||
|
|
||||||
|
#[error("The access method cannot be used in the requested operation")]
|
||||||
|
AccessMethodMismatch,
|
||||||
|
|
||||||
|
#[error("The access method does not exist")]
|
||||||
|
AccessNotFound,
|
||||||
|
|
||||||
|
#[error("This access method has an invalid duration")]
|
||||||
|
AccessInvalidDuration,
|
||||||
|
|
||||||
|
#[error("This access method results in an invalid expiration")]
|
||||||
|
AccessInvalidExpiration,
|
||||||
|
|
||||||
|
#[error("The record access signup query failed")]
|
||||||
|
AccessRecordSignupQueryFailed,
|
||||||
|
|
||||||
|
#[error("The record access signin query failed")]
|
||||||
|
AccessRecordSigninQueryFailed,
|
||||||
|
|
||||||
|
#[error("This record access method does not allow signup")]
|
||||||
|
AccessRecordNoSignup,
|
||||||
|
|
||||||
|
#[error("This record access method does not allow signin")]
|
||||||
|
AccessRecordNoSignin,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for String {
|
impl From<Error> for String {
|
||||||
|
|
|
@ -230,13 +230,13 @@ pub fn synchronous(ctx: &Context<'_>, name: &str, args: Vec<Value>) -> Result<Va
|
||||||
"rand::uuid::v7" => rand::uuid::v7,
|
"rand::uuid::v7" => rand::uuid::v7,
|
||||||
"rand::uuid" => rand::uuid,
|
"rand::uuid" => rand::uuid,
|
||||||
//
|
//
|
||||||
|
"session::ac" => session::ac(ctx),
|
||||||
"session::db" => session::db(ctx),
|
"session::db" => session::db(ctx),
|
||||||
"session::id" => session::id(ctx),
|
"session::id" => session::id(ctx),
|
||||||
"session::ip" => session::ip(ctx),
|
"session::ip" => session::ip(ctx),
|
||||||
"session::ns" => session::ns(ctx),
|
"session::ns" => session::ns(ctx),
|
||||||
"session::origin" => session::origin(ctx),
|
"session::origin" => session::origin(ctx),
|
||||||
"session::sc" => session::sc(ctx),
|
"session::rd" => session::rd(ctx),
|
||||||
"session::sd" => session::sd(ctx),
|
|
||||||
"session::token" => session::token(ctx),
|
"session::token" => session::token(ctx),
|
||||||
//
|
//
|
||||||
"string::concat" => string::concat,
|
"string::concat" => string::concat,
|
||||||
|
|
|
@ -12,7 +12,7 @@ impl_module_def!(
|
||||||
"ip" => run,
|
"ip" => run,
|
||||||
"ns" => run,
|
"ns" => run,
|
||||||
"origin" => run,
|
"origin" => run,
|
||||||
"sc" => run,
|
"ac" => run,
|
||||||
"sd" => run,
|
"rd" => run,
|
||||||
"token" => run
|
"token" => run
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
|
use crate::sql::paths::AC;
|
||||||
use crate::sql::paths::DB;
|
use crate::sql::paths::DB;
|
||||||
use crate::sql::paths::ID;
|
use crate::sql::paths::ID;
|
||||||
use crate::sql::paths::IP;
|
use crate::sql::paths::IP;
|
||||||
use crate::sql::paths::NS;
|
use crate::sql::paths::NS;
|
||||||
use crate::sql::paths::OR;
|
use crate::sql::paths::OR;
|
||||||
use crate::sql::paths::SC;
|
use crate::sql::paths::RD;
|
||||||
use crate::sql::paths::SD;
|
|
||||||
use crate::sql::paths::TK;
|
use crate::sql::paths::TK;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
|
|
||||||
|
pub fn ac(ctx: &Context, _: ()) -> Result<Value, Error> {
|
||||||
|
ctx.value("session").unwrap_or(&Value::None).pick(AC.as_ref()).ok()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn db(ctx: &Context, _: ()) -> Result<Value, Error> {
|
pub fn db(ctx: &Context, _: ()) -> Result<Value, Error> {
|
||||||
ctx.value("session").unwrap_or(&Value::None).pick(DB.as_ref()).ok()
|
ctx.value("session").unwrap_or(&Value::None).pick(DB.as_ref()).ok()
|
||||||
}
|
}
|
||||||
|
@ -30,12 +34,8 @@ pub fn origin(ctx: &Context, _: ()) -> Result<Value, Error> {
|
||||||
ctx.value("session").unwrap_or(&Value::None).pick(OR.as_ref()).ok()
|
ctx.value("session").unwrap_or(&Value::None).pick(OR.as_ref()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sc(ctx: &Context, _: ()) -> Result<Value, Error> {
|
pub fn rd(ctx: &Context, _: ()) -> Result<Value, Error> {
|
||||||
ctx.value("session").unwrap_or(&Value::None).pick(SC.as_ref()).ok()
|
ctx.value("session").unwrap_or(&Value::None).pick(RD.as_ref()).ok()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sd(ctx: &Context, _: ()) -> Result<Value, Error> {
|
|
||||||
ctx.value("session").unwrap_or(&Value::None).pick(SD.as_ref()).ok()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn token(ctx: &Context, _: ()) -> Result<Value, Error> {
|
pub fn token(ctx: &Context, _: ()) -> Result<Value, Error> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::sql::statements::{DefineTokenStatement, DefineUserStatement};
|
use crate::sql::statements::{DefineAccessStatement, DefineUserStatement};
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -49,9 +49,9 @@ impl Auth {
|
||||||
matches!(self.level(), Level::Database(_, _))
|
matches!(self.level(), Level::Database(_, _))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the current level is Scope
|
/// Check if the current level is Record
|
||||||
pub fn is_scope(&self) -> bool {
|
pub fn is_record(&self) -> bool {
|
||||||
matches!(self.level(), Level::Scope(_, _, _))
|
matches!(self.level(), Level::Record(_, _, _))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// System Auth helpers
|
/// System Auth helpers
|
||||||
|
@ -70,8 +70,8 @@ impl Auth {
|
||||||
Self::new(Actor::new("system_auth".into(), vec![role], (ns, db).into()))
|
Self::new(Actor::new("system_auth".into(), vec![role], (ns, db).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_sc(rid: String, ns: &str, db: &str, sc: &str) -> Self {
|
pub fn for_record(rid: String, ns: &str, db: &str, ac: &str) -> Self {
|
||||||
Self::new(Actor::new(rid, vec![], (ns, db, sc).into()))
|
Self::new(Actor::new(rid.to_string(), vec![], (ns, db, ac).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -95,8 +95,8 @@ impl std::convert::From<(&DefineUserStatement, Level)> for Auth {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<(&DefineTokenStatement, Level)> for Auth {
|
impl std::convert::From<(&DefineAccessStatement, Level)> for Auth {
|
||||||
fn from(val: (&DefineTokenStatement, Level)) -> Self {
|
fn from(val: (&DefineAccessStatement, Level)) -> Self {
|
||||||
Self::new((val.0, val.1).into())
|
Self::new((val.0, val.1).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||||
pub fn clear(session: &mut Session) -> Result<(), Error> {
|
pub fn clear(session: &mut Session) -> Result<(), Error> {
|
||||||
session.au = Arc::new(Auth::default());
|
session.au = Arc::new(Auth::default());
|
||||||
session.tk = None;
|
session.tk = None;
|
||||||
session.sc = None;
|
session.ac = None;
|
||||||
session.sd = None;
|
session.rd = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{Level, Resource, ResourceKind};
|
use super::{Level, Resource, ResourceKind};
|
||||||
use crate::iam::Role;
|
use crate::iam::Role;
|
||||||
use crate::sql::statements::{DefineTokenStatement, DefineUserStatement};
|
use crate::sql::statements::{DefineAccessStatement, DefineUserStatement};
|
||||||
|
|
||||||
//
|
//
|
||||||
// User
|
// User
|
||||||
|
@ -124,8 +124,8 @@ impl std::convert::From<(&DefineUserStatement, Level)> for Actor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<(&DefineTokenStatement, Level)> for Actor {
|
impl std::convert::From<(&DefineAccessStatement, Level)> for Actor {
|
||||||
fn from(val: (&DefineTokenStatement, Level)) -> Self {
|
fn from(val: (&DefineAccessStatement, Level)) -> Self {
|
||||||
Self::new(val.0.name.to_string(), Vec::default(), val.1)
|
Self::new(val.0.name.to_string(), Vec::default(), val.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub enum Level {
|
||||||
Root,
|
Root,
|
||||||
Namespace(String),
|
Namespace(String),
|
||||||
Database(String, String),
|
Database(String, String),
|
||||||
Scope(String, String, String),
|
Record(String, String, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Level {
|
impl std::fmt::Display for Level {
|
||||||
|
@ -27,7 +27,7 @@ impl std::fmt::Display for Level {
|
||||||
Level::Root => write!(f, "/"),
|
Level::Root => write!(f, "/"),
|
||||||
Level::Namespace(ns) => write!(f, "/ns:{ns}/"),
|
Level::Namespace(ns) => write!(f, "/ns:{ns}/"),
|
||||||
Level::Database(ns, db) => write!(f, "/ns:{ns}/db:{db}/"),
|
Level::Database(ns, db) => write!(f, "/ns:{ns}/db:{db}/"),
|
||||||
Level::Scope(ns, db, scope) => write!(f, "/ns:{ns}/db:{db}/scope:{scope}/"),
|
Level::Record(ns, db, id) => write!(f, "/ns:{ns}/db:{db}/id:{id}/"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ impl Level {
|
||||||
Level::Root => "Root",
|
Level::Root => "Root",
|
||||||
Level::Namespace(_) => "Namespace",
|
Level::Namespace(_) => "Namespace",
|
||||||
Level::Database(_, _) => "Database",
|
Level::Database(_, _) => "Database",
|
||||||
Level::Scope(_, _, _) => "Scope",
|
Level::Record(_, _, _) => "Record",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ impl Level {
|
||||||
match self {
|
match self {
|
||||||
Level::Namespace(ns) => Some(ns),
|
Level::Namespace(ns) => Some(ns),
|
||||||
Level::Database(ns, _) => Some(ns),
|
Level::Database(ns, _) => Some(ns),
|
||||||
Level::Scope(ns, _, _) => Some(ns),
|
Level::Record(ns, _, _) => Some(ns),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,14 +55,14 @@ impl Level {
|
||||||
pub fn db(&self) -> Option<&str> {
|
pub fn db(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Level::Database(_, db) => Some(db),
|
Level::Database(_, db) => Some(db),
|
||||||
Level::Scope(_, db, _) => Some(db),
|
Level::Record(_, db, _) => Some(db),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scope(&self) -> Option<&str> {
|
pub fn id(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Level::Scope(_, _, scope) => Some(scope),
|
Level::Record(_, _, id) => Some(id),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ impl Level {
|
||||||
Level::Root => None,
|
Level::Root => None,
|
||||||
Level::Namespace(_) => Some(Level::Root),
|
Level::Namespace(_) => Some(Level::Root),
|
||||||
Level::Database(ns, _) => Some(Level::Namespace(ns.to_owned())),
|
Level::Database(ns, _) => Some(Level::Namespace(ns.to_owned())),
|
||||||
Level::Scope(ns, db, _) => Some(Level::Database(ns.to_owned(), db.to_owned())),
|
Level::Record(ns, db, _) => Some(Level::Database(ns.to_owned(), db.to_owned())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +90,8 @@ impl Level {
|
||||||
attrs.insert("db".into(), RestrictedExpression::new_string(db.to_owned()));
|
attrs.insert("db".into(), RestrictedExpression::new_string(db.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(scope) = self.scope() {
|
if let Some(id) = self.id() {
|
||||||
attrs.insert("scope".into(), RestrictedExpression::new_string(scope.to_owned()));
|
attrs.insert("id".into(), RestrictedExpression::new_string(id.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs
|
attrs
|
||||||
|
@ -139,8 +139,8 @@ impl From<(&str, &str)> for Level {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(&str, &str, &str)> for Level {
|
impl From<(&str, &str, &str)> for Level {
|
||||||
fn from((ns, db, sc): (&str, &str, &str)) -> Self {
|
fn from((ns, db, id): (&str, &str, &str)) -> Self {
|
||||||
Level::Scope(ns.to_owned(), db.to_owned(), sc.to_owned())
|
Level::Record(ns.to_owned(), db.to_owned(), id.to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ impl From<(Option<&str>, Option<&str>, Option<&str>)> for Level {
|
||||||
(None, None, None) => ().into(),
|
(None, None, None) => ().into(),
|
||||||
(Some(ns), None, None) => (ns,).into(),
|
(Some(ns), None, None) => (ns,).into(),
|
||||||
(Some(ns), Some(db), None) => (ns, db).into(),
|
(Some(ns), Some(db), None) => (ns, db).into(),
|
||||||
(Some(ns), Some(db), Some(scope)) => (ns, db, scope).into(),
|
(Some(ns), Some(db), Some(id)) => (ns, db, id).into(),
|
||||||
_ => Level::No,
|
_ => Level::No,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub enum ResourceKind {
|
||||||
Any,
|
Any,
|
||||||
Namespace,
|
Namespace,
|
||||||
Database,
|
Database,
|
||||||
Scope,
|
Record,
|
||||||
Table,
|
Table,
|
||||||
Document,
|
Document,
|
||||||
Option,
|
Option,
|
||||||
|
@ -40,7 +40,7 @@ impl std::fmt::Display for ResourceKind {
|
||||||
ResourceKind::Any => write!(f, "Any"),
|
ResourceKind::Any => write!(f, "Any"),
|
||||||
ResourceKind::Namespace => write!(f, "Namespace"),
|
ResourceKind::Namespace => write!(f, "Namespace"),
|
||||||
ResourceKind::Database => write!(f, "Database"),
|
ResourceKind::Database => write!(f, "Database"),
|
||||||
ResourceKind::Scope => write!(f, "Scope"),
|
ResourceKind::Record => write!(f, "Record"),
|
||||||
ResourceKind::Table => write!(f, "Table"),
|
ResourceKind::Table => write!(f, "Table"),
|
||||||
ResourceKind::Document => write!(f, "Document"),
|
ResourceKind::Document => write!(f, "Document"),
|
||||||
ResourceKind::Option => write!(f, "Option"),
|
ResourceKind::Option => write!(f, "Option"),
|
||||||
|
@ -74,8 +74,8 @@ impl ResourceKind {
|
||||||
self.on_level((ns, db).into())
|
self.on_level((ns, db).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_scope(self, ns: &str, db: &str, scope: &str) -> Resource {
|
pub fn on_record(self, ns: &str, db: &str, rid: &str) -> Resource {
|
||||||
self.on_level((ns, db, scope).into())
|
self.on_level((ns, db, rid).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ pub static DEFAULT_CEDAR_SCHEMA: Lazy<serde_json::Value> = Lazy::new(|| {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"entityTypes": {
|
"entityTypes": {
|
||||||
// Represents the Root, Namespace, Database and Scope levels
|
// Represents the Root, Namespace, Database and Record levels
|
||||||
"Level": {
|
"Level": {
|
||||||
"shape": {
|
"shape": {
|
||||||
"type": "Record",
|
"type": "Record",
|
||||||
|
@ -24,7 +24,7 @@ pub static DEFAULT_CEDAR_SCHEMA: Lazy<serde_json::Value> = Lazy::new(|| {
|
||||||
"type": { "type": "String", "required": true },
|
"type": { "type": "String", "required": true },
|
||||||
"ns": { "type": "String", "required": false },
|
"ns": { "type": "String", "required": false },
|
||||||
"db": { "type": "String", "required": false },
|
"db": { "type": "String", "required": false },
|
||||||
"scope": { "type": "String", "required": false },
|
"rid": { "type": "String", "required": false },
|
||||||
"table": { "type": "String", "required": false },
|
"table": { "type": "String", "required": false },
|
||||||
"level" : { "type": "Entity", "name": "Level", "required": true },
|
"level" : { "type": "Entity", "name": "Level", "required": true },
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ pub static DEFAULT_CEDAR_SCHEMA: Lazy<serde_json::Value> = Lazy::new(|| {
|
||||||
"Any": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
"Any": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
||||||
"Namespace": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
"Namespace": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
||||||
"Database": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
"Database": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
||||||
"Scope": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
"Record": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
||||||
"Table": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
"Table": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
||||||
"Document": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
"Document": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
||||||
"Option": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
"Option": {"shape": {"type": "Resource"}, "memberOfTypes": ["Level"]},
|
||||||
|
@ -65,14 +65,14 @@ pub static DEFAULT_CEDAR_SCHEMA: Lazy<serde_json::Value> = Lazy::new(|| {
|
||||||
"View": {
|
"View": {
|
||||||
"appliesTo": {
|
"appliesTo": {
|
||||||
"principalTypes": [ "Actor" ],
|
"principalTypes": [ "Actor" ],
|
||||||
"resourceTypes": [ "Any", "Namespace", "Database", "Scope", "Table", "Document", "Option", "Function", "Analyzer", "Parameter", "Event", "Field", "Index", "Actor" ],
|
"resourceTypes": [ "Any", "Namespace", "Database", "Record", "Table", "Document", "Option", "Function", "Analyzer", "Parameter", "Event", "Field", "Index", "Actor" ],
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Edit": {
|
"Edit": {
|
||||||
"appliesTo": {
|
"appliesTo": {
|
||||||
"principalTypes": [ "Actor" ],
|
"principalTypes": [ "Actor" ],
|
||||||
"resourceTypes": [ "Any", "Namespace", "Database", "Scope", "Table", "Document", "Option", "Function", "Analyzer", "Parameter", "Event", "Field", "Index", "Actor" ],
|
"resourceTypes": [ "Any", "Namespace", "Database", "Record", "Table", "Document", "Option", "Function", "Analyzer", "Parameter", "Event", "Field", "Index", "Actor" ],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
43
core/src/iam/issue.rs
Normal file
43
core/src/iam/issue.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::sql::duration::Duration;
|
||||||
|
use crate::sql::Algorithm;
|
||||||
|
use chrono::Duration as ChronoDuration;
|
||||||
|
use chrono::Utc;
|
||||||
|
use jsonwebtoken::EncodingKey;
|
||||||
|
|
||||||
|
pub(crate) fn config(alg: Algorithm, key: String) -> Result<EncodingKey, Error> {
|
||||||
|
match alg {
|
||||||
|
Algorithm::Hs256 => Ok(EncodingKey::from_secret(key.as_ref())),
|
||||||
|
Algorithm::Hs384 => Ok(EncodingKey::from_secret(key.as_ref())),
|
||||||
|
Algorithm::Hs512 => Ok(EncodingKey::from_secret(key.as_ref())),
|
||||||
|
Algorithm::EdDSA => Ok(EncodingKey::from_ed_pem(key.as_ref())?),
|
||||||
|
Algorithm::Es256 => Ok(EncodingKey::from_ec_pem(key.as_ref())?),
|
||||||
|
Algorithm::Es384 => Ok(EncodingKey::from_ec_pem(key.as_ref())?),
|
||||||
|
Algorithm::Es512 => Ok(EncodingKey::from_ec_pem(key.as_ref())?),
|
||||||
|
Algorithm::Ps256 => Ok(EncodingKey::from_rsa_pem(key.as_ref())?),
|
||||||
|
Algorithm::Ps384 => Ok(EncodingKey::from_rsa_pem(key.as_ref())?),
|
||||||
|
Algorithm::Ps512 => Ok(EncodingKey::from_rsa_pem(key.as_ref())?),
|
||||||
|
Algorithm::Rs256 => Ok(EncodingKey::from_rsa_pem(key.as_ref())?),
|
||||||
|
Algorithm::Rs384 => Ok(EncodingKey::from_rsa_pem(key.as_ref())?),
|
||||||
|
Algorithm::Rs512 => Ok(EncodingKey::from_rsa_pem(key.as_ref())?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expiration(d: Option<Duration>) -> Result<Option<i64>, Error> {
|
||||||
|
let exp = match d {
|
||||||
|
Some(v) => {
|
||||||
|
// The defined duration must be valid
|
||||||
|
match ChronoDuration::from_std(v.0) {
|
||||||
|
// The resulting expiration must be valid
|
||||||
|
Ok(d) => match Utc::now().checked_add_signed(d) {
|
||||||
|
Some(exp) => Some(exp.timestamp()),
|
||||||
|
None => return Err(Error::AccessInvalidExpiration),
|
||||||
|
},
|
||||||
|
Err(_) => return Err(Error::AccessInvalidDuration),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(exp)
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ pub mod base;
|
||||||
pub mod check;
|
pub mod check;
|
||||||
pub mod clear;
|
pub mod clear;
|
||||||
pub mod entities;
|
pub mod entities;
|
||||||
|
pub mod issue;
|
||||||
#[cfg(feature = "jwks")]
|
#[cfg(feature = "jwks")]
|
||||||
pub mod jwks;
|
pub mod jwks;
|
||||||
pub mod policies;
|
pub mod policies;
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub static POLICY_SET: Lazy<PolicySet> = Lazy::new(|| {
|
||||||
) when {
|
) when {
|
||||||
principal.roles.contains(Role::"Editor") &&
|
principal.roles.contains(Role::"Editor") &&
|
||||||
resource.level in principal.level &&
|
resource.level in principal.level &&
|
||||||
["Namespace", "Database", "Scope", "Table", "Document", "Option", "Function", "Analyzer", "Parameter", "Event", "Field", "Index"].contains(resource.type)
|
["Namespace", "Database", "Record", "Table", "Document", "Option", "Function", "Analyzer", "Parameter", "Event", "Field", "Index"].contains(resource.type)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Owner role can edit all resources on the same level hierarchy or below
|
// Owner role can edit all resources on the same level hierarchy or below
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
use super::verify::{verify_creds_legacy, verify_db_creds, verify_ns_creds, verify_root_creds};
|
use super::verify::{verify_creds_legacy, verify_db_creds, verify_ns_creds, verify_root_creds};
|
||||||
use super::{Actor, Level};
|
use super::{Actor, Level};
|
||||||
use crate::cnf::{INSECURE_FORWARD_SCOPE_ERRORS, SERVER_NAME};
|
use crate::cnf::{INSECURE_FORWARD_RECORD_ACCESS_ERRORS, SERVER_NAME};
|
||||||
use crate::dbs::Session;
|
use crate::dbs::Session;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
|
use crate::iam::issue::{config, expiration};
|
||||||
use crate::iam::token::{Claims, HEADER};
|
use crate::iam::token::{Claims, HEADER};
|
||||||
use crate::iam::Auth;
|
use crate::iam::Auth;
|
||||||
use crate::kvs::{Datastore, LockType::*, TransactionType::*};
|
use crate::kvs::{Datastore, LockType::*, TransactionType::*};
|
||||||
|
use crate::sql::AccessType;
|
||||||
use crate::sql::Object;
|
use crate::sql::Object;
|
||||||
use crate::sql::Value;
|
use crate::sql::Value;
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use jsonwebtoken::{encode, EncodingKey};
|
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -21,20 +23,20 @@ pub async fn signin(
|
||||||
// Parse the specified variables
|
// Parse the specified variables
|
||||||
let ns = vars.get("NS").or_else(|| vars.get("ns"));
|
let ns = vars.get("NS").or_else(|| vars.get("ns"));
|
||||||
let db = vars.get("DB").or_else(|| vars.get("db"));
|
let db = vars.get("DB").or_else(|| vars.get("db"));
|
||||||
let sc = vars.get("SC").or_else(|| vars.get("sc"));
|
let ac = vars.get("AC").or_else(|| vars.get("ac"));
|
||||||
|
|
||||||
// Check if the parameters exist
|
// Check if the parameters exist
|
||||||
match (ns, db, sc) {
|
match (ns, db, ac) {
|
||||||
// SCOPE signin
|
// DB signin with access method
|
||||||
(Some(ns), Some(db), Some(sc)) => {
|
(Some(ns), Some(db), Some(ac)) => {
|
||||||
// Process the provided values
|
// Process the provided values
|
||||||
let ns = ns.to_raw_string();
|
let ns = ns.to_raw_string();
|
||||||
let db = db.to_raw_string();
|
let db = db.to_raw_string();
|
||||||
let sc = sc.to_raw_string();
|
let ac = ac.to_raw_string();
|
||||||
// Attempt to signin to specified scope
|
// Attempt to signin using specified access method
|
||||||
super::signin::sc(kvs, session, ns, db, sc, vars).await
|
super::signin::db_access(kvs, session, ns, db, ac, vars).await
|
||||||
}
|
}
|
||||||
// DB signin
|
// DB signin with user credentials
|
||||||
(Some(ns), Some(db), None) => {
|
(Some(ns), Some(db), None) => {
|
||||||
// Get the provided user and pass
|
// Get the provided user and pass
|
||||||
let user = vars.get("user");
|
let user = vars.get("user");
|
||||||
|
@ -49,12 +51,12 @@ pub async fn signin(
|
||||||
let user = user.to_raw_string();
|
let user = user.to_raw_string();
|
||||||
let pass = pass.to_raw_string();
|
let pass = pass.to_raw_string();
|
||||||
// Attempt to signin to database
|
// Attempt to signin to database
|
||||||
super::signin::db(kvs, session, ns, db, user, pass).await
|
super::signin::db_user(kvs, session, ns, db, user, pass).await
|
||||||
}
|
}
|
||||||
_ => Err(Error::MissingUserOrPass),
|
_ => Err(Error::MissingUserOrPass),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NS signin
|
// NS signin with user credentials
|
||||||
(Some(ns), None, None) => {
|
(Some(ns), None, None) => {
|
||||||
// Get the provided user and pass
|
// Get the provided user and pass
|
||||||
let user = vars.get("user");
|
let user = vars.get("user");
|
||||||
|
@ -68,12 +70,12 @@ pub async fn signin(
|
||||||
let user = user.to_raw_string();
|
let user = user.to_raw_string();
|
||||||
let pass = pass.to_raw_string();
|
let pass = pass.to_raw_string();
|
||||||
// Attempt to signin to namespace
|
// Attempt to signin to namespace
|
||||||
super::signin::ns(kvs, session, ns, user, pass).await
|
super::signin::ns_user(kvs, session, ns, user, pass).await
|
||||||
}
|
}
|
||||||
_ => Err(Error::MissingUserOrPass),
|
_ => Err(Error::MissingUserOrPass),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// KV signin
|
// ROOT signin with user credentials
|
||||||
(None, None, None) => {
|
(None, None, None) => {
|
||||||
// Get the provided user and pass
|
// Get the provided user and pass
|
||||||
let user = vars.get("user");
|
let user = vars.get("user");
|
||||||
|
@ -86,7 +88,7 @@ pub async fn signin(
|
||||||
let user = user.to_raw_string();
|
let user = user.to_raw_string();
|
||||||
let pass = pass.to_raw_string();
|
let pass = pass.to_raw_string();
|
||||||
// Attempt to signin to root
|
// Attempt to signin to root
|
||||||
super::signin::root(kvs, session, user, pass).await
|
super::signin::root_user(kvs, session, user, pass).await
|
||||||
}
|
}
|
||||||
_ => Err(Error::MissingUserOrPass),
|
_ => Err(Error::MissingUserOrPass),
|
||||||
}
|
}
|
||||||
|
@ -95,114 +97,112 @@ pub async fn signin(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sc(
|
pub async fn db_access(
|
||||||
kvs: &Datastore,
|
kvs: &Datastore,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
ns: String,
|
ns: String,
|
||||||
db: String,
|
db: String,
|
||||||
sc: String,
|
ac: String,
|
||||||
vars: Object,
|
vars: Object,
|
||||||
) -> Result<Option<String>, Error> {
|
) -> Result<Option<String>, Error> {
|
||||||
// Create a new readonly transaction
|
// Create a new readonly transaction
|
||||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||||
// Fetch the specified scope from storage
|
// Fetch the specified access method from storage
|
||||||
let scope = tx.get_sc(&ns, &db, &sc).await;
|
let access = tx.get_db_access(&ns, &db, &ac).await;
|
||||||
// Ensure that the transaction is cancelled
|
// Ensure that the transaction is cancelled
|
||||||
tx.cancel().await?;
|
tx.cancel().await?;
|
||||||
// Check if the supplied Scope login exists
|
// Check the provided access method exists
|
||||||
match scope {
|
match access {
|
||||||
Ok(sv) => {
|
Ok(av) => {
|
||||||
match sv.signin {
|
// Check the access method type
|
||||||
// This scope allows signin
|
// All access method types are supported except for JWT
|
||||||
Some(val) => {
|
// The JWT access method is the one that is internal to SurrealDB
|
||||||
// Setup the query params
|
// The equivalent of signing in with JWT is to authenticate it
|
||||||
let vars = Some(vars.0);
|
match av.kind {
|
||||||
// Setup the system session for finding the signin record
|
AccessType::Record(at) => {
|
||||||
let mut sess = Session::editor().with_ns(&ns).with_db(&db);
|
// Check if the record access method supports issuing tokens
|
||||||
sess.ip.clone_from(&session.ip);
|
let iss = match at.jwt.issue {
|
||||||
sess.or.clone_from(&session.or);
|
Some(iss) => iss,
|
||||||
// Compute the value with the params
|
_ => return Err(Error::AccessMethodMismatch),
|
||||||
match kvs.evaluate(val, &sess, vars).await {
|
};
|
||||||
// The signin value succeeded
|
match at.signin {
|
||||||
Ok(val) => match val.record() {
|
// This record access allows signin
|
||||||
// There is a record returned
|
Some(val) => {
|
||||||
Some(rid) => {
|
// Setup the query params
|
||||||
// Create the authentication key
|
let vars = Some(vars.0);
|
||||||
let key = EncodingKey::from_secret(sv.code.as_ref());
|
// Setup the system session for finding the signin record
|
||||||
// Create the authentication claim
|
let mut sess = Session::editor().with_ns(&ns).with_db(&db);
|
||||||
let exp = Some(
|
sess.ip.clone_from(&session.ip);
|
||||||
match sv.session {
|
sess.or.clone_from(&session.or);
|
||||||
Some(v) => {
|
// Compute the value with the params
|
||||||
// The defined session duration must be valid
|
match kvs.evaluate(val, &sess, vars).await {
|
||||||
match Duration::from_std(v.0) {
|
// The signin value succeeded
|
||||||
// The resulting session expiration must be valid
|
Ok(val) => {
|
||||||
Ok(d) => match Utc::now().checked_add_signed(d) {
|
match val.record() {
|
||||||
Some(exp) => exp,
|
// There is a record returned
|
||||||
None => {
|
Some(rid) => {
|
||||||
return Err(Error::InvalidSessionExpiration)
|
// Create the authentication key
|
||||||
}
|
let key = config(iss.alg, iss.key)?;
|
||||||
},
|
// Create the authentication claim
|
||||||
Err(_) => {
|
let val = Claims {
|
||||||
return Err(Error::InvalidSessionDuration)
|
iss: Some(SERVER_NAME.to_owned()),
|
||||||
}
|
iat: Some(Utc::now().timestamp()),
|
||||||
|
nbf: Some(Utc::now().timestamp()),
|
||||||
|
// Token expiration is derived from issuer duration
|
||||||
|
exp: expiration(iss.duration)?,
|
||||||
|
jti: Some(Uuid::new_v4().to_string()),
|
||||||
|
ns: Some(ns.to_owned()),
|
||||||
|
db: Some(db.to_owned()),
|
||||||
|
ac: Some(ac.to_owned()),
|
||||||
|
id: Some(rid.to_raw()),
|
||||||
|
..Claims::default()
|
||||||
|
};
|
||||||
|
// Log the authenticated access method info
|
||||||
|
trace!("Signing in with access method `{}`", ac);
|
||||||
|
// Create the authentication token
|
||||||
|
let enc =
|
||||||
|
encode(&Header::new(iss.alg.into()), &val, &key);
|
||||||
|
// Set the authentication on the session
|
||||||
|
session.tk = Some(val.into());
|
||||||
|
session.ns = Some(ns.to_owned());
|
||||||
|
session.db = Some(db.to_owned());
|
||||||
|
session.ac = Some(ac.to_owned());
|
||||||
|
session.rd = Some(Value::from(rid.to_owned()));
|
||||||
|
// Session expiration is derived from record access duration
|
||||||
|
session.exp = expiration(at.duration)?;
|
||||||
|
session.au = Arc::new(Auth::new(Actor::new(
|
||||||
|
rid.to_string(),
|
||||||
|
Default::default(),
|
||||||
|
Level::Record(ns, db, rid.to_string()),
|
||||||
|
)));
|
||||||
|
// Check the authentication token
|
||||||
|
match enc {
|
||||||
|
// The auth token was created successfully
|
||||||
|
Ok(tk) => Ok(Some(tk)),
|
||||||
|
_ => Err(Error::TokenMakingFailed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Utc::now() + Duration::hours(1),
|
_ => Err(Error::NoRecordFound),
|
||||||
}
|
}
|
||||||
.timestamp(),
|
|
||||||
);
|
|
||||||
let val = Claims {
|
|
||||||
iss: Some(SERVER_NAME.to_owned()),
|
|
||||||
iat: Some(Utc::now().timestamp()),
|
|
||||||
nbf: Some(Utc::now().timestamp()),
|
|
||||||
exp,
|
|
||||||
jti: Some(Uuid::new_v4().to_string()),
|
|
||||||
ns: Some(ns.to_owned()),
|
|
||||||
db: Some(db.to_owned()),
|
|
||||||
sc: Some(sc.to_owned()),
|
|
||||||
id: Some(rid.to_raw()),
|
|
||||||
..Claims::default()
|
|
||||||
};
|
|
||||||
// Log the authenticated scope info
|
|
||||||
trace!("Signing in to scope `{}`", sc);
|
|
||||||
// Create the authentication token
|
|
||||||
let enc = encode(&HEADER, &val, &key);
|
|
||||||
// Set the authentication on the session
|
|
||||||
session.tk = Some(val.into());
|
|
||||||
session.ns = Some(ns.to_owned());
|
|
||||||
session.db = Some(db.to_owned());
|
|
||||||
session.sc = Some(sc.to_owned());
|
|
||||||
session.sd = Some(Value::from(rid.to_owned()));
|
|
||||||
session.exp = exp;
|
|
||||||
session.au = Arc::new(Auth::new(Actor::new(
|
|
||||||
rid.to_string(),
|
|
||||||
Default::default(),
|
|
||||||
Level::Scope(ns, db, sc),
|
|
||||||
)));
|
|
||||||
// Check the authentication token
|
|
||||||
match enc {
|
|
||||||
// The auth token was created successfully
|
|
||||||
Ok(tk) => Ok(Some(tk)),
|
|
||||||
_ => Err(Error::TokenMakingFailed),
|
|
||||||
}
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
Error::Thrown(_) => Err(e),
|
||||||
|
e if *INSECURE_FORWARD_RECORD_ACCESS_ERRORS => Err(e),
|
||||||
|
_ => Err(Error::AccessRecordSigninQueryFailed),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
_ => Err(Error::NoRecordFound),
|
}
|
||||||
},
|
_ => Err(Error::AccessRecordNoSignin),
|
||||||
Err(e) => match e {
|
|
||||||
Error::Thrown(_) => Err(e),
|
|
||||||
e if *INSECURE_FORWARD_SCOPE_ERRORS => Err(e),
|
|
||||||
_ => Err(Error::SigninQueryFailed),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(Error::ScopeNoSignin),
|
_ => Err(Error::AccessMethodMismatch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(Error::NoScopeFound),
|
_ => Err(Error::AccessNotFound),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn db(
|
pub async fn db_user(
|
||||||
kvs: &Datastore,
|
kvs: &Datastore,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
ns: String,
|
ns: String,
|
||||||
|
@ -258,7 +258,7 @@ pub async fn db(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ns(
|
pub async fn ns_user(
|
||||||
kvs: &Datastore,
|
kvs: &Datastore,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
ns: String,
|
ns: String,
|
||||||
|
@ -312,7 +312,7 @@ pub async fn ns(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn root(
|
pub async fn root_user(
|
||||||
kvs: &Datastore,
|
kvs: &Datastore,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
user: String,
|
user: String,
|
||||||
|
@ -370,14 +370,14 @@ mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_signin_scope() {
|
async fn test_signin_record() {
|
||||||
// Test with correct credentials
|
// Test with correct credentials
|
||||||
{
|
{
|
||||||
let ds = Datastore::new("memory").await.unwrap();
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
let sess = Session::owner().with_ns("test").with_db("test");
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
ds.execute(
|
ds.execute(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE user SESSION 1h
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1h
|
||||||
SIGNIN (
|
SIGNIN (
|
||||||
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||||
)
|
)
|
||||||
|
@ -408,7 +408,7 @@ mod tests {
|
||||||
let mut vars: HashMap<&str, Value> = HashMap::new();
|
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||||
vars.insert("user", "user".into());
|
vars.insert("user", "user".into());
|
||||||
vars.insert("pass", "pass".into());
|
vars.insert("pass", "pass".into());
|
||||||
let res = sc(
|
let res = db_access(
|
||||||
&ds,
|
&ds,
|
||||||
&mut sess,
|
&mut sess,
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
|
@ -422,10 +422,11 @@ mod tests {
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.au.id(), "user:test");
|
assert_eq!(sess.au.id(), "user:test");
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
assert_eq!(sess.au.level().db(), Some("test"));
|
assert_eq!(sess.au.level().db(), Some("test"));
|
||||||
// Scope users should not have roles
|
assert_eq!(sess.au.level().id(), Some("user:test"));
|
||||||
|
// Record users should not have roles
|
||||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||||
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
||||||
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
||||||
|
@ -436,7 +437,7 @@ mod tests {
|
||||||
let max_exp = (Utc::now() + Duration::hours(1) + Duration::seconds(10)).timestamp();
|
let max_exp = (Utc::now() + Duration::hours(1) + Duration::seconds(10)).timestamp();
|
||||||
assert!(
|
assert!(
|
||||||
exp > min_exp && exp < max_exp,
|
exp > min_exp && exp < max_exp,
|
||||||
"Session expiration is expected to follow scope duration"
|
"Session expiration is expected to follow access method duration"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,7 +447,7 @@ mod tests {
|
||||||
let sess = Session::owner().with_ns("test").with_db("test");
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
ds.execute(
|
ds.execute(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE user SESSION 1h
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1h
|
||||||
SIGNIN (
|
SIGNIN (
|
||||||
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||||
)
|
)
|
||||||
|
@ -477,7 +478,7 @@ mod tests {
|
||||||
let mut vars: HashMap<&str, Value> = HashMap::new();
|
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||||
vars.insert("user", "user".into());
|
vars.insert("user", "user".into());
|
||||||
vars.insert("pass", "incorrect".into());
|
vars.insert("pass", "incorrect".into());
|
||||||
let res = sc(
|
let res = db_access(
|
||||||
&ds,
|
&ds,
|
||||||
&mut sess,
|
&mut sess,
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
|
@ -492,7 +493,161 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_signin_db() {
|
async fn test_signin_record_with_jwt_issuer() {
|
||||||
|
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||||
|
// Test with correct credentials
|
||||||
|
{
|
||||||
|
let public_key = r#"-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
|
||||||
|
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
|
||||||
|
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
|
||||||
|
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
|
||||||
|
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
|
||||||
|
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
|
||||||
|
mwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----"#;
|
||||||
|
let private_key = r#"-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj
|
||||||
|
MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu
|
||||||
|
NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ
|
||||||
|
qgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulg
|
||||||
|
p2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlR
|
||||||
|
ZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwi
|
||||||
|
VuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskV
|
||||||
|
laAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8
|
||||||
|
sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83H
|
||||||
|
mQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwY
|
||||||
|
dgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cw
|
||||||
|
ta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQ
|
||||||
|
DM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2T
|
||||||
|
N0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t
|
||||||
|
0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPv
|
||||||
|
t8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDU
|
||||||
|
AhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk
|
||||||
|
48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISL
|
||||||
|
DY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnK
|
||||||
|
xt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEA
|
||||||
|
mNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh
|
||||||
|
2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfz
|
||||||
|
et6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhr
|
||||||
|
VBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicD
|
||||||
|
TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc
|
||||||
|
dn/RsYEONbwQSjIfMPkvxF+8HQ==
|
||||||
|
-----END PRIVATE KEY-----"#;
|
||||||
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
|
ds.execute(
|
||||||
|
&format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS user ON DATABASE TYPE RECORD
|
||||||
|
DURATION 1h
|
||||||
|
SIGNIN (
|
||||||
|
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||||
|
)
|
||||||
|
SIGNUP (
|
||||||
|
CREATE user CONTENT {{
|
||||||
|
name: $user,
|
||||||
|
pass: crypto::argon2::generate($pass)
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
WITH JWT ALGORITHM RS256 KEY '{public_key}'
|
||||||
|
WITH ISSUER KEY '{private_key}' DURATION 15m
|
||||||
|
;
|
||||||
|
|
||||||
|
CREATE user:test CONTENT {{
|
||||||
|
name: 'user',
|
||||||
|
pass: crypto::argon2::generate('pass')
|
||||||
|
}}
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&sess,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Signin with the user
|
||||||
|
let mut sess = Session {
|
||||||
|
ns: Some("test".to_string()),
|
||||||
|
db: Some("test".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||||
|
vars.insert("user", "user".into());
|
||||||
|
vars.insert("pass", "pass".into());
|
||||||
|
let res = db_access(
|
||||||
|
&ds,
|
||||||
|
&mut sess,
|
||||||
|
"test".to_string(),
|
||||||
|
"test".to_string(),
|
||||||
|
"user".to_string(),
|
||||||
|
vars.into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||||
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
|
assert_eq!(sess.au.id(), "user:test");
|
||||||
|
assert!(sess.au.is_record());
|
||||||
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
|
assert_eq!(sess.au.level().db(), Some("test"));
|
||||||
|
assert_eq!(sess.au.level().id(), Some("user:test"));
|
||||||
|
// Record users should not have roles
|
||||||
|
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||||
|
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
||||||
|
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
||||||
|
// Session expiration should always be set for tokens issued by SurrealDB
|
||||||
|
let exp = sess.exp.unwrap();
|
||||||
|
// Expiration should match the current time plus session duration with some margin
|
||||||
|
let min_sess_exp =
|
||||||
|
(Utc::now() + Duration::hours(1) - Duration::seconds(10)).timestamp();
|
||||||
|
let max_sess_exp =
|
||||||
|
(Utc::now() + Duration::hours(1) + Duration::seconds(10)).timestamp();
|
||||||
|
assert!(
|
||||||
|
exp > min_sess_exp && exp < max_sess_exp,
|
||||||
|
"Session expiration is expected to follow access method duration"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Decode token and check that it has been issued as intended
|
||||||
|
if let Ok(Some(tk)) = res {
|
||||||
|
// Check that token can be verified with the defined algorithm
|
||||||
|
let val = Validation::new(Algorithm::RS256);
|
||||||
|
// Check that token can be verified with the defined public key
|
||||||
|
let token_data = decode::<Claims>(
|
||||||
|
&tk,
|
||||||
|
&DecodingKey::from_rsa_pem(public_key.as_ref()).unwrap(),
|
||||||
|
&val,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// Check that token has been issued with the defined algorithm
|
||||||
|
assert_eq!(token_data.header.alg, Algorithm::RS256);
|
||||||
|
// Check that token expiration matches the defined duration
|
||||||
|
// Expiration should match the current time plus token duration with some margin
|
||||||
|
let exp = match token_data.claims.exp {
|
||||||
|
Some(exp) => exp,
|
||||||
|
_ => panic!("Token is missing expiration claim"),
|
||||||
|
};
|
||||||
|
let min_tk_exp =
|
||||||
|
(Utc::now() + Duration::minutes(15) - Duration::seconds(10)).timestamp();
|
||||||
|
let max_tk_exp =
|
||||||
|
(Utc::now() + Duration::minutes(15) + Duration::seconds(10)).timestamp();
|
||||||
|
assert!(
|
||||||
|
exp > min_tk_exp && exp < max_tk_exp,
|
||||||
|
"Token expiration is expected to follow issuer duration"
|
||||||
|
);
|
||||||
|
// Check required token claims
|
||||||
|
assert_eq!(token_data.claims.ns, Some("test".to_string()));
|
||||||
|
assert_eq!(token_data.claims.db, Some("test".to_string()));
|
||||||
|
assert_eq!(token_data.claims.id, Some("user:test".to_string()));
|
||||||
|
assert_eq!(token_data.claims.ac, Some("user".to_string()));
|
||||||
|
} else {
|
||||||
|
panic!("Token could not be extracted from result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_signin_db_user() {
|
||||||
//
|
//
|
||||||
// Test without roles defined
|
// Test without roles defined
|
||||||
//
|
//
|
||||||
|
@ -507,7 +662,7 @@ mod tests {
|
||||||
db: Some("test".to_string()),
|
db: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res = db(
|
let res = db_user(
|
||||||
&ds,
|
&ds,
|
||||||
&mut sess,
|
&mut sess,
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
|
@ -546,7 +701,7 @@ mod tests {
|
||||||
db: Some("test".to_string()),
|
db: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res = db(
|
let res = db_user(
|
||||||
&ds,
|
&ds,
|
||||||
&mut sess,
|
&mut sess,
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
|
@ -578,7 +733,7 @@ mod tests {
|
||||||
let mut sess = Session {
|
let mut sess = Session {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res = db(
|
let res = db_user(
|
||||||
&ds,
|
&ds,
|
||||||
&mut sess,
|
&mut sess,
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
|
@ -593,7 +748,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_signin_ns() {
|
async fn test_signin_ns_user() {
|
||||||
//
|
//
|
||||||
// Test without roles defined
|
// Test without roles defined
|
||||||
//
|
//
|
||||||
|
@ -608,7 +763,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res =
|
let res =
|
||||||
ns(&ds, &mut sess, "test".to_string(), "user".to_string(), "pass".to_string())
|
ns_user(&ds, &mut sess, "test".to_string(), "user".to_string(), "pass".to_string())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||||
|
@ -638,7 +793,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res =
|
let res =
|
||||||
ns(&ds, &mut sess, "test".to_string(), "user".to_string(), "pass".to_string())
|
ns_user(&ds, &mut sess, "test".to_string(), "user".to_string(), "pass".to_string())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||||
|
@ -661,16 +816,21 @@ mod tests {
|
||||||
let mut sess = Session {
|
let mut sess = Session {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res =
|
let res = ns_user(
|
||||||
ns(&ds, &mut sess, "test".to_string(), "user".to_string(), "invalid".to_string())
|
&ds,
|
||||||
.await;
|
&mut sess,
|
||||||
|
"test".to_string(),
|
||||||
|
"user".to_string(),
|
||||||
|
"invalid".to_string(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
|
assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_signin_root() {
|
async fn test_signin_root_user() {
|
||||||
//
|
//
|
||||||
// Test without roles defined
|
// Test without roles defined
|
||||||
//
|
//
|
||||||
|
@ -683,7 +843,7 @@ mod tests {
|
||||||
let mut sess = Session {
|
let mut sess = Session {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res = root(&ds, &mut sess, "user".to_string(), "pass".to_string()).await;
|
let res = root_user(&ds, &mut sess, "user".to_string(), "pass".to_string()).await;
|
||||||
|
|
||||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||||
assert_eq!(sess.au.id(), "user");
|
assert_eq!(sess.au.id(), "user");
|
||||||
|
@ -708,7 +868,7 @@ mod tests {
|
||||||
let mut sess = Session {
|
let mut sess = Session {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res = root(&ds, &mut sess, "user".to_string(), "pass".to_string()).await;
|
let res = root_user(&ds, &mut sess, "user".to_string(), "pass".to_string()).await;
|
||||||
|
|
||||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||||
assert_eq!(sess.au.id(), "user");
|
assert_eq!(sess.au.id(), "user");
|
||||||
|
@ -728,7 +888,7 @@ mod tests {
|
||||||
let mut sess = Session {
|
let mut sess = Session {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let res = root(&ds, &mut sess, "user".to_string(), "invalid".to_string()).await;
|
let res = root_user(&ds, &mut sess, "user".to_string(), "invalid".to_string()).await;
|
||||||
|
|
||||||
assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
|
assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use crate::cnf::{INSECURE_FORWARD_SCOPE_ERRORS, SERVER_NAME};
|
use crate::cnf::{INSECURE_FORWARD_RECORD_ACCESS_ERRORS, SERVER_NAME};
|
||||||
use crate::dbs::Session;
|
use crate::dbs::Session;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::iam::token::{Claims, HEADER};
|
use crate::iam::issue::{config, expiration};
|
||||||
|
use crate::iam::token::Claims;
|
||||||
use crate::iam::Auth;
|
use crate::iam::Auth;
|
||||||
use crate::iam::{Actor, Level};
|
use crate::iam::{Actor, Level};
|
||||||
use crate::kvs::{Datastore, LockType::*, TransactionType::*};
|
use crate::kvs::{Datastore, LockType::*, TransactionType::*};
|
||||||
|
use crate::sql::AccessType;
|
||||||
use crate::sql::Object;
|
use crate::sql::Object;
|
||||||
use crate::sql::Value;
|
use crate::sql::Value;
|
||||||
use chrono::{Duration, Utc};
|
use chrono::Utc;
|
||||||
use jsonwebtoken::{encode, EncodingKey};
|
use jsonwebtoken::{encode, Header};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -20,125 +22,122 @@ pub async fn signup(
|
||||||
// Parse the specified variables
|
// Parse the specified variables
|
||||||
let ns = vars.get("NS").or_else(|| vars.get("ns"));
|
let ns = vars.get("NS").or_else(|| vars.get("ns"));
|
||||||
let db = vars.get("DB").or_else(|| vars.get("db"));
|
let db = vars.get("DB").or_else(|| vars.get("db"));
|
||||||
let sc = vars.get("SC").or_else(|| vars.get("sc"));
|
let ac = vars.get("AC").or_else(|| vars.get("ac"));
|
||||||
// Check if the parameters exist
|
// Check if the parameters exist
|
||||||
match (ns, db, sc) {
|
match (ns, db, ac) {
|
||||||
(Some(ns), Some(db), Some(sc)) => {
|
(Some(ns), Some(db), Some(ac)) => {
|
||||||
// Process the provided values
|
// Process the provided values
|
||||||
let ns = ns.to_raw_string();
|
let ns = ns.to_raw_string();
|
||||||
let db = db.to_raw_string();
|
let db = db.to_raw_string();
|
||||||
let sc = sc.to_raw_string();
|
let ac = ac.to_raw_string();
|
||||||
// Attempt to signup to specified scope
|
// Attempt to signup using specified access method
|
||||||
super::signup::sc(kvs, session, ns, db, sc, vars).await
|
// Currently, signup is only supported at the database level
|
||||||
|
super::signup::db_access(kvs, session, ns, db, ac, vars).await
|
||||||
}
|
}
|
||||||
_ => Err(Error::InvalidSignup),
|
_ => Err(Error::InvalidSignup),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sc(
|
pub async fn db_access(
|
||||||
kvs: &Datastore,
|
kvs: &Datastore,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
ns: String,
|
ns: String,
|
||||||
db: String,
|
db: String,
|
||||||
sc: String,
|
ac: String,
|
||||||
vars: Object,
|
vars: Object,
|
||||||
) -> Result<Option<String>, Error> {
|
) -> Result<Option<String>, Error> {
|
||||||
// Create a new readonly transaction
|
// Create a new readonly transaction
|
||||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||||
// Fetch the specified scope from storage
|
// Fetch the specified access method from storage
|
||||||
let scope = tx.get_sc(&ns, &db, &sc).await;
|
let access = tx.get_db_access(&ns, &db, &ac).await;
|
||||||
// Ensure that the transaction is cancelled
|
// Ensure that the transaction is cancelled
|
||||||
tx.cancel().await?;
|
tx.cancel().await?;
|
||||||
// Check if the supplied Scope login exists
|
// Check the provided access method exists
|
||||||
match scope {
|
match access {
|
||||||
Ok(sv) => {
|
Ok(av) => {
|
||||||
match sv.signup {
|
// Check the access method type
|
||||||
// This scope allows signup
|
// Currently, only the record access method supports signup
|
||||||
Some(val) => {
|
match av.kind {
|
||||||
// Setup the query params
|
AccessType::Record(at) => {
|
||||||
let vars = Some(vars.0);
|
// Check if the record access method supports issuing tokens
|
||||||
// Setup the system session for creating the signup record
|
let iss = match at.jwt.issue {
|
||||||
let mut sess = Session::editor().with_ns(&ns).with_db(&db);
|
Some(iss) => iss,
|
||||||
sess.ip.clone_from(&session.ip);
|
_ => return Err(Error::AccessMethodMismatch),
|
||||||
sess.or.clone_from(&session.or);
|
};
|
||||||
// Compute the value with the params
|
match at.signup {
|
||||||
match kvs.evaluate(val, &sess, vars).await {
|
// This record access allows signup
|
||||||
// The signin value succeeded
|
Some(val) => {
|
||||||
Ok(val) => match val.record() {
|
// Setup the query params
|
||||||
// There is a record returned
|
let vars = Some(vars.0);
|
||||||
Some(rid) => {
|
// Setup the system session for finding the signup record
|
||||||
// Create the authentication key
|
let mut sess = Session::editor().with_ns(&ns).with_db(&db);
|
||||||
let key = EncodingKey::from_secret(sv.code.as_ref());
|
sess.ip.clone_from(&session.ip);
|
||||||
// Create the authentication claim
|
sess.or.clone_from(&session.or);
|
||||||
let exp = Some(
|
// Compute the value with the params
|
||||||
match sv.session {
|
match kvs.evaluate(val, &sess, vars).await {
|
||||||
Some(v) => {
|
// The signin value succeeded
|
||||||
// The defined session duration must be valid
|
Ok(val) => {
|
||||||
match Duration::from_std(v.0) {
|
match val.record() {
|
||||||
// The resulting session expiration must be valid
|
// There is a record returned
|
||||||
Ok(d) => match Utc::now().checked_add_signed(d) {
|
Some(rid) => {
|
||||||
Some(exp) => exp,
|
// Create the authentication key
|
||||||
None => {
|
let key = config(iss.alg, iss.key)?;
|
||||||
return Err(Error::InvalidSessionExpiration)
|
// Create the authentication claim
|
||||||
}
|
let val = Claims {
|
||||||
},
|
iss: Some(SERVER_NAME.to_owned()),
|
||||||
Err(_) => {
|
iat: Some(Utc::now().timestamp()),
|
||||||
return Err(Error::InvalidSessionDuration)
|
nbf: Some(Utc::now().timestamp()),
|
||||||
}
|
// Token expiration is derived from issuer duration
|
||||||
|
exp: expiration(iss.duration)?,
|
||||||
|
jti: Some(Uuid::new_v4().to_string()),
|
||||||
|
ns: Some(ns.to_owned()),
|
||||||
|
db: Some(db.to_owned()),
|
||||||
|
ac: Some(ac.to_owned()),
|
||||||
|
id: Some(rid.to_raw()),
|
||||||
|
..Claims::default()
|
||||||
|
};
|
||||||
|
// Log the authenticated access method info
|
||||||
|
trace!("Signing up with access method `{}`", ac);
|
||||||
|
// Create the authentication token
|
||||||
|
let enc =
|
||||||
|
encode(&Header::new(iss.alg.into()), &val, &key);
|
||||||
|
// Set the authentication on the session
|
||||||
|
session.tk = Some(val.into());
|
||||||
|
session.ns = Some(ns.to_owned());
|
||||||
|
session.db = Some(db.to_owned());
|
||||||
|
session.ac = Some(ac.to_owned());
|
||||||
|
session.rd = Some(Value::from(rid.to_owned()));
|
||||||
|
// Session expiration is derived from record access duration
|
||||||
|
session.exp = expiration(at.duration)?;
|
||||||
|
session.au = Arc::new(Auth::new(Actor::new(
|
||||||
|
rid.to_string(),
|
||||||
|
Default::default(),
|
||||||
|
Level::Record(ns, db, rid.to_string()),
|
||||||
|
)));
|
||||||
|
// Check the authentication token
|
||||||
|
match enc {
|
||||||
|
// The auth token was created successfully
|
||||||
|
Ok(tk) => Ok(Some(tk)),
|
||||||
|
_ => Err(Error::TokenMakingFailed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Utc::now() + Duration::hours(1),
|
_ => Err(Error::NoRecordFound),
|
||||||
}
|
}
|
||||||
.timestamp(),
|
|
||||||
);
|
|
||||||
let val = Claims {
|
|
||||||
iss: Some(SERVER_NAME.to_owned()),
|
|
||||||
iat: Some(Utc::now().timestamp()),
|
|
||||||
nbf: Some(Utc::now().timestamp()),
|
|
||||||
jti: Some(Uuid::new_v4().to_string()),
|
|
||||||
exp,
|
|
||||||
ns: Some(ns.to_owned()),
|
|
||||||
db: Some(db.to_owned()),
|
|
||||||
sc: Some(sc.to_owned()),
|
|
||||||
id: Some(rid.to_raw()),
|
|
||||||
..Claims::default()
|
|
||||||
};
|
|
||||||
// Log the authenticated scope info
|
|
||||||
trace!("Signing up to scope `{}`", sc);
|
|
||||||
// Create the authentication token
|
|
||||||
let enc = encode(&HEADER, &val, &key);
|
|
||||||
// Set the authentication on the session
|
|
||||||
session.tk = Some(val.into());
|
|
||||||
session.ns = Some(ns.to_owned());
|
|
||||||
session.db = Some(db.to_owned());
|
|
||||||
session.sc = Some(sc.to_owned());
|
|
||||||
session.sd = Some(Value::from(rid.to_owned()));
|
|
||||||
session.exp = exp;
|
|
||||||
session.au = Arc::new(Auth::new(Actor::new(
|
|
||||||
rid.to_string(),
|
|
||||||
Default::default(),
|
|
||||||
Level::Scope(ns, db, sc),
|
|
||||||
)));
|
|
||||||
// Create the authentication token
|
|
||||||
match enc {
|
|
||||||
// The auth token was created successfully
|
|
||||||
Ok(tk) => Ok(Some(tk)),
|
|
||||||
_ => Err(Error::TokenMakingFailed),
|
|
||||||
}
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
Error::Thrown(_) => Err(e),
|
||||||
|
e if *INSECURE_FORWARD_RECORD_ACCESS_ERRORS => Err(e),
|
||||||
|
_ => Err(Error::AccessRecordSignupQueryFailed),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
_ => Err(Error::NoRecordFound),
|
}
|
||||||
},
|
_ => Err(Error::AccessRecordNoSignup),
|
||||||
Err(e) => match e {
|
|
||||||
Error::Thrown(_) => Err(e),
|
|
||||||
e if *INSECURE_FORWARD_SCOPE_ERRORS => Err(e),
|
|
||||||
_ => Err(Error::SignupQueryFailed),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(Error::ScopeNoSignup),
|
_ => Err(Error::AccessMethodMismatch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(Error::NoScopeFound),
|
_ => Err(Error::AccessNotFound),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,17 +145,18 @@ pub async fn sc(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::iam::Role;
|
use crate::iam::Role;
|
||||||
|
use chrono::Duration;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_scope_signup() {
|
async fn test_record_signup() {
|
||||||
// Test with valid parameters
|
// Test with valid parameters
|
||||||
{
|
{
|
||||||
let ds = Datastore::new("memory").await.unwrap();
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
let sess = Session::owner().with_ns("test").with_db("test");
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
ds.execute(
|
ds.execute(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE user SESSION 1h
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1h
|
||||||
SIGNIN (
|
SIGNIN (
|
||||||
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||||
)
|
)
|
||||||
|
@ -182,7 +182,7 @@ mod tests {
|
||||||
let mut vars: HashMap<&str, Value> = HashMap::new();
|
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||||
vars.insert("user", "user".into());
|
vars.insert("user", "user".into());
|
||||||
vars.insert("pass", "pass".into());
|
vars.insert("pass", "pass".into());
|
||||||
let res = sc(
|
let res = db_access(
|
||||||
&ds,
|
&ds,
|
||||||
&mut sess,
|
&mut sess,
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
|
@ -196,10 +196,10 @@ mod tests {
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert!(sess.au.id().starts_with("user:"));
|
assert!(sess.au.id().starts_with("user:"));
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
assert_eq!(sess.au.level().db(), Some("test"));
|
assert_eq!(sess.au.level().db(), Some("test"));
|
||||||
// Scope users should not have roles.
|
// Record users should not have roles.
|
||||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||||
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
||||||
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
||||||
|
@ -210,7 +210,7 @@ mod tests {
|
||||||
let max_exp = (Utc::now() + Duration::hours(1) + Duration::seconds(10)).timestamp();
|
let max_exp = (Utc::now() + Duration::hours(1) + Duration::seconds(10)).timestamp();
|
||||||
assert!(
|
assert!(
|
||||||
exp > min_exp && exp < max_exp,
|
exp > min_exp && exp < max_exp,
|
||||||
"Session expiration is expected to follow scope duration"
|
"Session expiration is expected to follow token duration"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ mod tests {
|
||||||
let sess = Session::owner().with_ns("test").with_db("test");
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
ds.execute(
|
ds.execute(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE user SESSION 1h
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1h
|
||||||
SIGNIN (
|
SIGNIN (
|
||||||
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||||
)
|
)
|
||||||
|
@ -246,7 +246,7 @@ mod tests {
|
||||||
let mut vars: HashMap<&str, Value> = HashMap::new();
|
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||||
// Password is missing
|
// Password is missing
|
||||||
vars.insert("user", "user".into());
|
vars.insert("user", "user".into());
|
||||||
let res = sc(
|
let res = db_access(
|
||||||
&ds,
|
&ds,
|
||||||
&mut sess,
|
&mut sess,
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
|
@ -259,4 +259,158 @@ mod tests {
|
||||||
assert!(res.is_err(), "Unexpected successful signup: {:?}", res);
|
assert!(res.is_err(), "Unexpected successful signup: {:?}", res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_record_signup_with_jwt_issuer() {
|
||||||
|
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||||
|
// Test with valid parameters
|
||||||
|
{
|
||||||
|
let public_key = r#"-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
|
||||||
|
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
|
||||||
|
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
|
||||||
|
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
|
||||||
|
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
|
||||||
|
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
|
||||||
|
mwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----"#;
|
||||||
|
let private_key = r#"-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj
|
||||||
|
MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu
|
||||||
|
NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ
|
||||||
|
qgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulg
|
||||||
|
p2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlR
|
||||||
|
ZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwi
|
||||||
|
VuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskV
|
||||||
|
laAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8
|
||||||
|
sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83H
|
||||||
|
mQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwY
|
||||||
|
dgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cw
|
||||||
|
ta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQ
|
||||||
|
DM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2T
|
||||||
|
N0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t
|
||||||
|
0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPv
|
||||||
|
t8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDU
|
||||||
|
AhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk
|
||||||
|
48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISL
|
||||||
|
DY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnK
|
||||||
|
xt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEA
|
||||||
|
mNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh
|
||||||
|
2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfz
|
||||||
|
et6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhr
|
||||||
|
VBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicD
|
||||||
|
TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc
|
||||||
|
dn/RsYEONbwQSjIfMPkvxF+8HQ==
|
||||||
|
-----END PRIVATE KEY-----"#;
|
||||||
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
|
ds.execute(
|
||||||
|
&format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS user ON DATABASE TYPE RECORD
|
||||||
|
DURATION 1h
|
||||||
|
SIGNIN (
|
||||||
|
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||||
|
)
|
||||||
|
SIGNUP (
|
||||||
|
CREATE user CONTENT {{
|
||||||
|
name: $user,
|
||||||
|
pass: crypto::argon2::generate($pass)
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
WITH JWT ALGORITHM RS256 KEY '{public_key}'
|
||||||
|
WITH ISSUER KEY '{private_key}' DURATION 15m
|
||||||
|
;
|
||||||
|
|
||||||
|
CREATE user:test CONTENT {{
|
||||||
|
name: 'user',
|
||||||
|
pass: crypto::argon2::generate('pass')
|
||||||
|
}}
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&sess,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Signin with the user
|
||||||
|
let mut sess = Session {
|
||||||
|
ns: Some("test".to_string()),
|
||||||
|
db: Some("test".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||||
|
vars.insert("user", "user".into());
|
||||||
|
vars.insert("pass", "pass".into());
|
||||||
|
let res = db_access(
|
||||||
|
&ds,
|
||||||
|
&mut sess,
|
||||||
|
"test".to_string(),
|
||||||
|
"test".to_string(),
|
||||||
|
"user".to_string(),
|
||||||
|
vars.into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(res.is_ok(), "Failed to signup: {:?}", res);
|
||||||
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
|
assert!(sess.au.id().starts_with("user:"));
|
||||||
|
assert!(sess.au.is_record());
|
||||||
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
|
assert_eq!(sess.au.level().db(), Some("test"));
|
||||||
|
// Record users should not have roles.
|
||||||
|
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||||
|
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
||||||
|
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
||||||
|
// Session expiration should always be set for tokens issued by SurrealDB
|
||||||
|
let exp = sess.exp.unwrap();
|
||||||
|
// Expiration should match the current time plus session duration with some margin
|
||||||
|
let min_sess_exp =
|
||||||
|
(Utc::now() + Duration::hours(1) - Duration::seconds(10)).timestamp();
|
||||||
|
let max_sess_exp =
|
||||||
|
(Utc::now() + Duration::hours(1) + Duration::seconds(10)).timestamp();
|
||||||
|
assert!(
|
||||||
|
exp > min_sess_exp && exp < max_sess_exp,
|
||||||
|
"Session expiration is expected to follow access method duration"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Decode token and check that it has been issued as intended
|
||||||
|
if let Ok(Some(tk)) = res {
|
||||||
|
// Check that token can be verified with the defined algorithm
|
||||||
|
let val = Validation::new(Algorithm::RS256);
|
||||||
|
// Check that token can be verified with the defined public key
|
||||||
|
let token_data = decode::<Claims>(
|
||||||
|
&tk,
|
||||||
|
&DecodingKey::from_rsa_pem(public_key.as_ref()).unwrap(),
|
||||||
|
&val,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// Check that token has been issued with the defined algorithm
|
||||||
|
assert_eq!(token_data.header.alg, Algorithm::RS256);
|
||||||
|
// Check that token expiration matches the defined duration
|
||||||
|
// Expiration should match the current time plus token duration with some margin
|
||||||
|
let exp = match token_data.claims.exp {
|
||||||
|
Some(exp) => exp,
|
||||||
|
_ => panic!("Token is missing expiration claim"),
|
||||||
|
};
|
||||||
|
let min_tk_exp =
|
||||||
|
(Utc::now() + Duration::minutes(15) - Duration::seconds(10)).timestamp();
|
||||||
|
let max_tk_exp =
|
||||||
|
(Utc::now() + Duration::minutes(15) + Duration::seconds(10)).timestamp();
|
||||||
|
assert!(
|
||||||
|
exp > min_tk_exp && exp < max_tk_exp,
|
||||||
|
"Token expiration is expected to follow issuer duration"
|
||||||
|
);
|
||||||
|
// Check required token claims
|
||||||
|
assert_eq!(token_data.claims.ns, Some("test".to_string()));
|
||||||
|
assert_eq!(token_data.claims.db, Some("test".to_string()));
|
||||||
|
assert!(token_data.claims.id.unwrap().starts_with("user:"));
|
||||||
|
assert_eq!(token_data.claims.ac, Some("user".to_string()));
|
||||||
|
} else {
|
||||||
|
panic!("Token could not be extracted from result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,20 +35,13 @@ pub struct Claims {
|
||||||
#[serde(alias = "https://surrealdb.com/database")]
|
#[serde(alias = "https://surrealdb.com/database")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub db: Option<String>,
|
pub db: Option<String>,
|
||||||
#[serde(alias = "sc")]
|
#[serde(alias = "ac")]
|
||||||
#[serde(alias = "SC")]
|
#[serde(alias = "AC")]
|
||||||
#[serde(rename = "SC")]
|
#[serde(rename = "AC")]
|
||||||
#[serde(alias = "https://surrealdb.com/sc")]
|
#[serde(alias = "https://surrealdb.com/ac")]
|
||||||
#[serde(alias = "https://surrealdb.com/scope")]
|
#[serde(alias = "https://surrealdb.com/access")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub sc: Option<String>,
|
pub ac: Option<String>,
|
||||||
#[serde(alias = "tk")]
|
|
||||||
#[serde(alias = "TK")]
|
|
||||||
#[serde(rename = "TK")]
|
|
||||||
#[serde(alias = "https://surrealdb.com/tk")]
|
|
||||||
#[serde(alias = "https://surrealdb.com/token")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub tk: Option<String>,
|
|
||||||
#[serde(alias = "id")]
|
#[serde(alias = "id")]
|
||||||
#[serde(alias = "ID")]
|
#[serde(alias = "ID")]
|
||||||
#[serde(rename = "ID")]
|
#[serde(rename = "ID")]
|
||||||
|
@ -101,13 +94,9 @@ impl From<Claims> for Value {
|
||||||
if let Some(db) = v.db {
|
if let Some(db) = v.db {
|
||||||
out.insert("DB".to_string(), db.into());
|
out.insert("DB".to_string(), db.into());
|
||||||
}
|
}
|
||||||
// Add SC field if set
|
// Add AC field if set
|
||||||
if let Some(sc) = v.sc {
|
if let Some(ac) = v.ac {
|
||||||
out.insert("SC".to_string(), sc.into());
|
out.insert("AC".to_string(), ac.into());
|
||||||
}
|
|
||||||
// Add TK field if set
|
|
||||||
if let Some(tk) = v.tk {
|
|
||||||
out.insert("TK".to_string(), tk.into());
|
|
||||||
}
|
}
|
||||||
// Add ID field if set
|
// Add ID field if set
|
||||||
if let Some(id) = v.id {
|
if let Some(id) = v.id {
|
||||||
|
|
|
@ -4,94 +4,70 @@ use crate::err::Error;
|
||||||
use crate::iam::jwks;
|
use crate::iam::jwks;
|
||||||
use crate::iam::{token::Claims, Actor, Auth, Level, Role};
|
use crate::iam::{token::Claims, Actor, Auth, Level, Role};
|
||||||
use crate::kvs::{Datastore, LockType::*, TransactionType::*};
|
use crate::kvs::{Datastore, LockType::*, TransactionType::*};
|
||||||
|
use crate::sql::access_type::{AccessType, JwtAccessVerify};
|
||||||
use crate::sql::{statements::DefineUserStatement, Algorithm, Value};
|
use crate::sql::{statements::DefineUserStatement, Algorithm, Value};
|
||||||
use crate::syn;
|
use crate::syn;
|
||||||
use argon2::{Argon2, PasswordHash, PasswordVerifier};
|
use argon2::{Argon2, PasswordHash, PasswordVerifier};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use jsonwebtoken::{decode, DecodingKey, Header, Validation};
|
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
async fn config(
|
fn config(alg: Algorithm, key: String) -> Result<(DecodingKey, Validation), Error> {
|
||||||
_kvs: &Datastore,
|
match alg {
|
||||||
de_kind: Algorithm,
|
|
||||||
de_code: String,
|
|
||||||
_token_header: Header,
|
|
||||||
) -> Result<(DecodingKey, Validation), Error> {
|
|
||||||
if de_kind == Algorithm::Jwks {
|
|
||||||
#[cfg(not(feature = "jwks"))]
|
|
||||||
{
|
|
||||||
warn!("Failed to verify a token defined as JWKS when the feature is not enabled");
|
|
||||||
Err(Error::InvalidAuth)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "jwks")]
|
|
||||||
// The key identifier header must be present
|
|
||||||
if let Some(kid) = _token_header.kid {
|
|
||||||
jwks::config(_kvs, &kid, &de_code, _token_header.alg).await
|
|
||||||
} else {
|
|
||||||
Err(Error::MissingTokenHeader("kid".to_string()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
config_alg(de_kind, de_code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_alg(algo: Algorithm, code: String) -> Result<(DecodingKey, Validation), Error> {
|
|
||||||
match algo {
|
|
||||||
Algorithm::Hs256 => Ok((
|
Algorithm::Hs256 => Ok((
|
||||||
DecodingKey::from_secret(code.as_ref()),
|
DecodingKey::from_secret(key.as_ref()),
|
||||||
Validation::new(jsonwebtoken::Algorithm::HS256),
|
Validation::new(jsonwebtoken::Algorithm::HS256),
|
||||||
)),
|
)),
|
||||||
Algorithm::Hs384 => Ok((
|
Algorithm::Hs384 => Ok((
|
||||||
DecodingKey::from_secret(code.as_ref()),
|
DecodingKey::from_secret(key.as_ref()),
|
||||||
Validation::new(jsonwebtoken::Algorithm::HS384),
|
Validation::new(jsonwebtoken::Algorithm::HS384),
|
||||||
)),
|
)),
|
||||||
Algorithm::Hs512 => Ok((
|
Algorithm::Hs512 => Ok((
|
||||||
DecodingKey::from_secret(code.as_ref()),
|
DecodingKey::from_secret(key.as_ref()),
|
||||||
Validation::new(jsonwebtoken::Algorithm::HS512),
|
Validation::new(jsonwebtoken::Algorithm::HS512),
|
||||||
)),
|
)),
|
||||||
Algorithm::EdDSA => Ok((
|
Algorithm::EdDSA => Ok((
|
||||||
DecodingKey::from_ed_pem(code.as_ref())?,
|
DecodingKey::from_ed_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::EdDSA),
|
Validation::new(jsonwebtoken::Algorithm::EdDSA),
|
||||||
)),
|
)),
|
||||||
Algorithm::Es256 => Ok((
|
Algorithm::Es256 => Ok((
|
||||||
DecodingKey::from_ec_pem(code.as_ref())?,
|
DecodingKey::from_ec_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::ES256),
|
Validation::new(jsonwebtoken::Algorithm::ES256),
|
||||||
)),
|
)),
|
||||||
Algorithm::Es384 => Ok((
|
Algorithm::Es384 => Ok((
|
||||||
DecodingKey::from_ec_pem(code.as_ref())?,
|
DecodingKey::from_ec_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::ES384),
|
Validation::new(jsonwebtoken::Algorithm::ES384),
|
||||||
)),
|
)),
|
||||||
Algorithm::Es512 => Ok((
|
Algorithm::Es512 => Ok((
|
||||||
DecodingKey::from_ec_pem(code.as_ref())?,
|
DecodingKey::from_ec_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::ES384),
|
Validation::new(jsonwebtoken::Algorithm::ES384),
|
||||||
)),
|
)),
|
||||||
Algorithm::Ps256 => Ok((
|
Algorithm::Ps256 => Ok((
|
||||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::PS256),
|
Validation::new(jsonwebtoken::Algorithm::PS256),
|
||||||
)),
|
)),
|
||||||
Algorithm::Ps384 => Ok((
|
Algorithm::Ps384 => Ok((
|
||||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::PS384),
|
Validation::new(jsonwebtoken::Algorithm::PS384),
|
||||||
)),
|
)),
|
||||||
Algorithm::Ps512 => Ok((
|
Algorithm::Ps512 => Ok((
|
||||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::PS512),
|
Validation::new(jsonwebtoken::Algorithm::PS512),
|
||||||
)),
|
)),
|
||||||
Algorithm::Rs256 => Ok((
|
Algorithm::Rs256 => Ok((
|
||||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::RS256),
|
Validation::new(jsonwebtoken::Algorithm::RS256),
|
||||||
)),
|
)),
|
||||||
Algorithm::Rs384 => Ok((
|
Algorithm::Rs384 => Ok((
|
||||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::RS384),
|
Validation::new(jsonwebtoken::Algorithm::RS384),
|
||||||
)),
|
)),
|
||||||
Algorithm::Rs512 => Ok((
|
Algorithm::Rs512 => Ok((
|
||||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||||
Validation::new(jsonwebtoken::Algorithm::RS512),
|
Validation::new(jsonwebtoken::Algorithm::RS512),
|
||||||
)),
|
)),
|
||||||
Algorithm::Jwks => Err(Error::InvalidAuth), // We should never get here
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,96 +191,87 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
}
|
}
|
||||||
// Check the token authentication claims
|
// Check the token authentication claims
|
||||||
match token_data.claims {
|
match token_data.claims {
|
||||||
// Check if this is scope token authentication
|
// Check if this is record access
|
||||||
Claims {
|
Claims {
|
||||||
ns: Some(ns),
|
ns: Some(ns),
|
||||||
db: Some(db),
|
db: Some(db),
|
||||||
sc: Some(sc),
|
ac: Some(ac),
|
||||||
tk: Some(tk),
|
|
||||||
id,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
// Log the decoded authentication claims
|
|
||||||
trace!("Authenticating to scope `{}` with token `{}`", sc, tk);
|
|
||||||
// Create a new readonly transaction
|
|
||||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
|
||||||
// Parse the record id
|
|
||||||
let id = match id {
|
|
||||||
Some(id) => syn::thing(&id)?.into(),
|
|
||||||
None => Value::None,
|
|
||||||
};
|
|
||||||
// Get the scope token
|
|
||||||
let de = tx.get_sc_token(&ns, &db, &sc, &tk).await?;
|
|
||||||
// Obtain the configuration with which to verify the token
|
|
||||||
let cf = config(kvs, de.kind, de.code, token_data.header).await?;
|
|
||||||
// Verify the token
|
|
||||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
|
||||||
// Log the success
|
|
||||||
debug!("Authenticated to scope `{}` with token `{}`", sc, tk);
|
|
||||||
// Set the session
|
|
||||||
session.sd = Some(id);
|
|
||||||
session.tk = Some(value);
|
|
||||||
session.ns = Some(ns.to_owned());
|
|
||||||
session.db = Some(db.to_owned());
|
|
||||||
session.sc = Some(sc.to_owned());
|
|
||||||
session.exp = token_data.claims.exp;
|
|
||||||
session.au = Arc::new(Auth::new(Actor::new(
|
|
||||||
de.name.to_string(),
|
|
||||||
Default::default(),
|
|
||||||
Level::Scope(ns, db, sc),
|
|
||||||
)));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// Check if this is scope authentication
|
|
||||||
Claims {
|
|
||||||
ns: Some(ns),
|
|
||||||
db: Some(db),
|
|
||||||
sc: Some(sc),
|
|
||||||
id: Some(id),
|
id: Some(id),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
// Log the decoded authentication claims
|
// Log the decoded authentication claims
|
||||||
trace!("Authenticating to scope `{}`", sc);
|
trace!("Authenticating with record access method `{}`", ac);
|
||||||
// Create a new readonly transaction
|
// Create a new readonly transaction
|
||||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||||
// Parse the record id
|
// Parse the record id
|
||||||
let id = syn::thing(&id)?;
|
let id = syn::thing(&id)?;
|
||||||
// Get the scope
|
// Get the database access method
|
||||||
let de = tx.get_sc(&ns, &db, &sc).await?;
|
let de = tx.get_db_access(&ns, &db, &ac).await?;
|
||||||
let cf = config_alg(Algorithm::Hs512, de.code)?;
|
// Obtain the configuration to verify the token based on the access method
|
||||||
|
let cf = match de.kind {
|
||||||
|
AccessType::Record(ac) => match ac.jwt.verify {
|
||||||
|
JwtAccessVerify::Key(key) => config(key.alg, key.key),
|
||||||
|
#[cfg(feature = "jwks")]
|
||||||
|
JwtAccessVerify::Jwks(jwks) => {
|
||||||
|
if let Some(kid) = token_data.header.kid {
|
||||||
|
jwks::config(kvs, &kid, &jwks.url, token_data.header.alg).await
|
||||||
|
} else {
|
||||||
|
Err(Error::MissingTokenHeader("kid".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "jwks"))]
|
||||||
|
_ => return Err(Error::AccessMethodMismatch),
|
||||||
|
},
|
||||||
|
_ => return Err(Error::AccessMethodMismatch),
|
||||||
|
}?;
|
||||||
// Verify the token
|
// Verify the token
|
||||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||||
// Log the success
|
// Log the success
|
||||||
debug!("Authenticated to scope `{}`", sc);
|
debug!("Authenticated with record access method `{}`", ac);
|
||||||
// Set the session
|
// Set the session
|
||||||
session.tk = Some(value);
|
session.tk = Some(value);
|
||||||
session.ns = Some(ns.to_owned());
|
session.ns = Some(ns.to_owned());
|
||||||
session.db = Some(db.to_owned());
|
session.db = Some(db.to_owned());
|
||||||
session.sc = Some(sc.to_owned());
|
session.ac = Some(ac.to_owned());
|
||||||
session.sd = Some(Value::from(id.to_owned()));
|
session.rd = Some(Value::from(id.to_owned()));
|
||||||
session.exp = token_data.claims.exp;
|
session.exp = token_data.claims.exp;
|
||||||
session.au = Arc::new(Auth::new(Actor::new(
|
session.au = Arc::new(Auth::new(Actor::new(
|
||||||
id.to_string(),
|
id.to_string(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Level::Scope(ns, db, sc),
|
Level::Record(ns, db, id.to_string()),
|
||||||
)));
|
)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
// Check if this is database token authentication
|
// Check if this is database access
|
||||||
Claims {
|
Claims {
|
||||||
ns: Some(ns),
|
ns: Some(ns),
|
||||||
db: Some(db),
|
db: Some(db),
|
||||||
tk: Some(tk),
|
ac: Some(ac),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
// Log the decoded authentication claims
|
// Log the decoded authentication claims
|
||||||
trace!("Authenticating to database `{}` with token `{}`", db, tk);
|
trace!("Authenticating to database `{}` with access method `{}`", db, ac);
|
||||||
// Create a new readonly transaction
|
// Create a new readonly transaction
|
||||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||||
// Get the database token
|
// Get the database access method
|
||||||
let de = tx.get_db_token(&ns, &db, &tk).await?;
|
let de = tx.get_db_access(&ns, &db, &ac).await?;
|
||||||
// Obtain the configuration with which to verify the token
|
// Obtain the configuration to verify the token based on the access method
|
||||||
let cf = config(kvs, de.kind, de.code, token_data.header).await?;
|
let cf = match de.kind {
|
||||||
|
AccessType::Jwt(ac) => match ac.verify {
|
||||||
|
JwtAccessVerify::Key(key) => config(key.alg, key.key),
|
||||||
|
#[cfg(feature = "jwks")]
|
||||||
|
JwtAccessVerify::Jwks(jwks) => {
|
||||||
|
if let Some(kid) = token_data.header.kid {
|
||||||
|
jwks::config(kvs, &kid, &jwks.url, token_data.header.alg).await
|
||||||
|
} else {
|
||||||
|
Err(Error::MissingTokenHeader("kid".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "jwks"))]
|
||||||
|
_ => return Err(Error::AccessMethodMismatch),
|
||||||
|
},
|
||||||
|
_ => return Err(Error::AccessMethodMismatch),
|
||||||
|
}?;
|
||||||
// Verify the token
|
// Verify the token
|
||||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||||
// Parse the roles
|
// Parse the roles
|
||||||
|
@ -320,11 +287,12 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
};
|
};
|
||||||
// Log the success
|
// Log the success
|
||||||
debug!("Authenticated to database `{}` with token `{}`", db, tk);
|
debug!("Authenticated to database `{}` with access method `{}`", db, ac);
|
||||||
// Set the session
|
// Set the session
|
||||||
session.tk = Some(value);
|
session.tk = Some(value);
|
||||||
session.ns = Some(ns.to_owned());
|
session.ns = Some(ns.to_owned());
|
||||||
session.db = Some(db.to_owned());
|
session.db = Some(db.to_owned());
|
||||||
|
session.ac = Some(ac.to_owned());
|
||||||
session.exp = token_data.claims.exp;
|
session.exp = token_data.claims.exp;
|
||||||
session.au = Arc::new(Auth::new(Actor::new(
|
session.au = Arc::new(Auth::new(Actor::new(
|
||||||
de.name.to_string(),
|
de.name.to_string(),
|
||||||
|
@ -333,7 +301,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
)));
|
)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
// Check if this is database authentication
|
// Check if this is database authentication with user credentials
|
||||||
Claims {
|
Claims {
|
||||||
ns: Some(ns),
|
ns: Some(ns),
|
||||||
db: Some(db),
|
db: Some(db),
|
||||||
|
@ -349,7 +317,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
trace!("Error while authenticating to database `{db}`: {e}");
|
trace!("Error while authenticating to database `{db}`: {e}");
|
||||||
Error::InvalidAuth
|
Error::InvalidAuth
|
||||||
})?;
|
})?;
|
||||||
let cf = config_alg(Algorithm::Hs512, de.code)?;
|
let cf = config(Algorithm::Hs512, de.code)?;
|
||||||
// Verify the token
|
// Verify the token
|
||||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||||
// Log the success
|
// Log the success
|
||||||
|
@ -366,20 +334,35 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
)));
|
)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
// Check if this is namespace token authentication
|
// Check if this is namespace access
|
||||||
Claims {
|
Claims {
|
||||||
ns: Some(ns),
|
ns: Some(ns),
|
||||||
tk: Some(tk),
|
ac: Some(ac),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
// Log the decoded authentication claims
|
// Log the decoded authentication claims
|
||||||
trace!("Authenticating to namespace `{}` with token `{}`", ns, tk);
|
trace!("Authenticating to namespace `{}` with access method `{}`", ns, ac);
|
||||||
// Create a new readonly transaction
|
// Create a new readonly transaction
|
||||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||||
// Get the namespace token
|
// Get the namespace access method
|
||||||
let de = tx.get_ns_token(&ns, &tk).await?;
|
let de = tx.get_ns_access(&ns, &ac).await?;
|
||||||
// Obtain the configuration with which to verify the token
|
// Obtain the configuration to verify the token based on the access method
|
||||||
let cf = config(kvs, de.kind, de.code, token_data.header).await?;
|
let cf = match de.kind {
|
||||||
|
AccessType::Jwt(ac) => match ac.verify {
|
||||||
|
JwtAccessVerify::Key(key) => config(key.alg, key.key),
|
||||||
|
#[cfg(feature = "jwks")]
|
||||||
|
JwtAccessVerify::Jwks(jwks) => {
|
||||||
|
if let Some(kid) = token_data.header.kid {
|
||||||
|
jwks::config(kvs, &kid, &jwks.url, token_data.header.alg).await
|
||||||
|
} else {
|
||||||
|
Err(Error::MissingTokenHeader("kid".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "jwks"))]
|
||||||
|
_ => return Err(Error::AccessMethodMismatch),
|
||||||
|
},
|
||||||
|
_ => return Err(Error::AccessMethodMismatch),
|
||||||
|
}?;
|
||||||
// Verify the token
|
// Verify the token
|
||||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||||
// Parse the roles
|
// Parse the roles
|
||||||
|
@ -395,16 +378,17 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
};
|
};
|
||||||
// Log the success
|
// Log the success
|
||||||
trace!("Authenticated to namespace `{}` with token `{}`", ns, tk);
|
trace!("Authenticated to namespace `{}` with access method `{}`", ns, ac);
|
||||||
// Set the session
|
// Set the session
|
||||||
session.tk = Some(value);
|
session.tk = Some(value);
|
||||||
session.ns = Some(ns.to_owned());
|
session.ns = Some(ns.to_owned());
|
||||||
|
session.ac = Some(ac.to_owned());
|
||||||
session.exp = token_data.claims.exp;
|
session.exp = token_data.claims.exp;
|
||||||
session.au =
|
session.au =
|
||||||
Arc::new(Auth::new(Actor::new(de.name.to_string(), roles, Level::Namespace(ns))));
|
Arc::new(Auth::new(Actor::new(de.name.to_string(), roles, Level::Namespace(ns))));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
// Check if this is namespace authentication
|
// Check if this is namespace authentication with user credentials
|
||||||
Claims {
|
Claims {
|
||||||
ns: Some(ns),
|
ns: Some(ns),
|
||||||
id: Some(id),
|
id: Some(id),
|
||||||
|
@ -419,7 +403,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
trace!("Error while authenticating to namespace `{ns}`: {e}");
|
trace!("Error while authenticating to namespace `{ns}`: {e}");
|
||||||
Error::InvalidAuth
|
Error::InvalidAuth
|
||||||
})?;
|
})?;
|
||||||
let cf = config_alg(Algorithm::Hs512, de.code)?;
|
let cf = config(Algorithm::Hs512, de.code)?;
|
||||||
// Verify the token
|
// Verify the token
|
||||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||||
// Log the success
|
// Log the success
|
||||||
|
@ -435,7 +419,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
)));
|
)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
// Check if this is root level authentication
|
// Check if this is root authentication with user credentials
|
||||||
Claims {
|
Claims {
|
||||||
id: Some(id),
|
id: Some(id),
|
||||||
..
|
..
|
||||||
|
@ -449,7 +433,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
trace!("Error while authenticating to root: {e}");
|
trace!("Error while authenticating to root: {e}");
|
||||||
Error::InvalidAuth
|
Error::InvalidAuth
|
||||||
})?;
|
})?;
|
||||||
let cf = config_alg(Algorithm::Hs512, de.code)?;
|
let cf = config(Algorithm::Hs512, de.code)?;
|
||||||
// Verify the token
|
// Verify the token
|
||||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||||
// Log the success
|
// Log the success
|
||||||
|
@ -814,7 +798,7 @@ mod tests {
|
||||||
iat: Some(Utc::now().timestamp()),
|
iat: Some(Utc::now().timestamp()),
|
||||||
nbf: Some(Utc::now().timestamp()),
|
nbf: Some(Utc::now().timestamp()),
|
||||||
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
tk: Some("token".to_string()),
|
ac: Some("token".to_string()),
|
||||||
ns: Some("test".to_string()),
|
ns: Some("test".to_string()),
|
||||||
..Claims::default()
|
..Claims::default()
|
||||||
};
|
};
|
||||||
|
@ -822,7 +806,7 @@ mod tests {
|
||||||
let ds = Datastore::new("memory").await.unwrap();
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
let sess = Session::owner().with_ns("test").with_db("test");
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
ds.execute(
|
ds.execute(
|
||||||
format!("DEFINE TOKEN token ON NS TYPE HS512 VALUE '{secret}'").as_str(),
|
format!("DEFINE ACCESS token ON NS TYPE JWT ALGORITHM HS512 KEY '{secret}'").as_str(),
|
||||||
&sess,
|
&sess,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -921,7 +905,7 @@ mod tests {
|
||||||
iat: Some(Utc::now().timestamp()),
|
iat: Some(Utc::now().timestamp()),
|
||||||
nbf: Some(Utc::now().timestamp()),
|
nbf: Some(Utc::now().timestamp()),
|
||||||
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
tk: Some("token".to_string()),
|
ac: Some("token".to_string()),
|
||||||
ns: Some("test".to_string()),
|
ns: Some("test".to_string()),
|
||||||
db: Some("test".to_string()),
|
db: Some("test".to_string()),
|
||||||
..Claims::default()
|
..Claims::default()
|
||||||
|
@ -930,7 +914,8 @@ mod tests {
|
||||||
let ds = Datastore::new("memory").await.unwrap();
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
let sess = Session::owner().with_ns("test").with_db("test");
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
ds.execute(
|
ds.execute(
|
||||||
format!("DEFINE TOKEN token ON DB TYPE HS512 VALUE '{secret}'").as_str(),
|
format!("DEFINE ACCESS token ON DATABASE TYPE JWT ALGORITHM HS512 KEY '{secret}'")
|
||||||
|
.as_str(),
|
||||||
&sess,
|
&sess,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -1023,7 +1008,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_token_scope() {
|
async fn test_token_db_record() {
|
||||||
let secret = "jwt_secret";
|
let secret = "jwt_secret";
|
||||||
let key = EncodingKey::from_secret(secret.as_ref());
|
let key = EncodingKey::from_secret(secret.as_ref());
|
||||||
let claims = Claims {
|
let claims = Claims {
|
||||||
|
@ -1031,17 +1016,25 @@ mod tests {
|
||||||
iat: Some(Utc::now().timestamp()),
|
iat: Some(Utc::now().timestamp()),
|
||||||
nbf: Some(Utc::now().timestamp()),
|
nbf: Some(Utc::now().timestamp()),
|
||||||
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
tk: Some("token".to_string()),
|
|
||||||
ns: Some("test".to_string()),
|
ns: Some("test".to_string()),
|
||||||
db: Some("test".to_string()),
|
db: Some("test".to_string()),
|
||||||
sc: Some("test".to_string()),
|
ac: Some("token".to_string()),
|
||||||
|
id: Some("user:test".to_string()),
|
||||||
..Claims::default()
|
..Claims::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let ds = Datastore::new("memory").await.unwrap();
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
let sess = Session::owner().with_ns("test").with_db("test");
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
ds.execute(
|
ds.execute(
|
||||||
format!("DEFINE TOKEN token ON SCOPE test TYPE HS512 VALUE '{secret}';").as_str(),
|
format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS token ON DATABASE TYPE RECORD
|
||||||
|
WITH JWT ALGORITHM HS512 KEY '{secret}';
|
||||||
|
|
||||||
|
CREATE user:test;
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
&sess,
|
&sess,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -1050,7 +1043,7 @@ mod tests {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Test without roles defined
|
// Test without roles defined
|
||||||
// Roles should be ignored in scope authentication
|
// Roles should be ignored in record access
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
// Prepare the claims object
|
// Prepare the claims object
|
||||||
|
@ -1065,9 +1058,9 @@ mod tests {
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.sc, Some("test".to_string()));
|
assert_eq!(sess.ac, Some("token".to_string()));
|
||||||
assert_eq!(sess.au.id(), "token");
|
assert_eq!(sess.au.id(), "user:test");
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
assert_eq!(sess.au.level().db(), Some("test"));
|
assert_eq!(sess.au.level().db(), Some("test"));
|
||||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||||
|
@ -1078,7 +1071,7 @@ mod tests {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Test with roles defined
|
// Test with roles defined
|
||||||
// Roles should be ignored in scope authentication
|
// Roles should be ignored in record access
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
// Prepare the claims object
|
// Prepare the claims object
|
||||||
|
@ -1093,9 +1086,9 @@ mod tests {
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.sc, Some("test".to_string()));
|
assert_eq!(sess.ac, Some("token".to_string()));
|
||||||
assert_eq!(sess.au.id(), "token");
|
assert_eq!(sess.au.id(), "user:test");
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
assert_eq!(sess.au.level().db(), Some("test"));
|
assert_eq!(sess.au.level().db(), Some("test"));
|
||||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||||
|
@ -1121,12 +1114,12 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Test with valid token invalid sc
|
// Test with valid token invalid access method
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
// Prepare the claims object
|
// Prepare the claims object
|
||||||
let mut claims = claims.clone();
|
let mut claims = claims.clone();
|
||||||
claims.sc = Some("invalid".to_string());
|
claims.ac = Some("invalid".to_string());
|
||||||
// Create the token
|
// Create the token
|
||||||
let enc = encode(&HEADER, &claims, &key).unwrap();
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
// Signin with the token
|
// Signin with the token
|
||||||
|
@ -1156,7 +1149,7 @@ mod tests {
|
||||||
// Test with generic user identifier
|
// Test with generic user identifier
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
let resource_id = "user:`2k9qnabxuxh8k4d5gfto`".to_string();
|
let resource_id = "user:⟨2k9qnabxuxh8k4d5gfto⟩".to_string();
|
||||||
// Prepare the claims object
|
// Prepare the claims object
|
||||||
let mut claims = claims.clone();
|
let mut claims = claims.clone();
|
||||||
claims.id = Some(resource_id.clone());
|
claims.id = Some(resource_id.clone());
|
||||||
|
@ -1169,11 +1162,11 @@ mod tests {
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.sc, Some("test".to_string()));
|
assert_eq!(sess.ac, Some("token".to_string()));
|
||||||
assert_eq!(sess.au.id(), "token");
|
assert_eq!(sess.au.id(), resource_id);
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
let user_id = syn::thing(&resource_id).unwrap();
|
let user_id = syn::thing(&resource_id).unwrap();
|
||||||
assert_eq!(sess.sd, Some(Value::from(user_id)));
|
assert_eq!(sess.rd, Some(Value::from(user_id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -1195,11 +1188,11 @@ mod tests {
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.sc, Some("test".to_string()));
|
assert_eq!(sess.ac, Some("token".to_string()));
|
||||||
assert_eq!(sess.au.id(), "token");
|
assert_eq!(sess.au.id(), resource_id);
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
let user_id = syn::thing(&resource_id).unwrap();
|
let user_id = syn::thing(&resource_id).unwrap();
|
||||||
assert_eq!(sess.sd, Some(Value::from(user_id)));
|
assert_eq!(sess.rd, Some(Value::from(user_id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1222,11 +1215,11 @@ mod tests {
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.sc, Some("test".to_string()));
|
assert_eq!(sess.ac, Some("token".to_string()));
|
||||||
assert_eq!(sess.au.id(), "token");
|
assert_eq!(sess.au.id(), resource_id);
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
let user_id = syn::thing(&resource_id).unwrap();
|
let user_id = syn::thing(&resource_id).unwrap();
|
||||||
assert_eq!(sess.sd, Some(Value::from(user_id)));
|
assert_eq!(sess.rd, Some(Value::from(user_id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1250,11 +1243,11 @@ mod tests {
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.sc, Some("test".to_string()));
|
assert_eq!(sess.ac, Some("token".to_string()));
|
||||||
assert_eq!(sess.au.id(), "token");
|
assert_eq!(sess.au.id(), resource_id);
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
let user_id = syn::thing(&resource_id).unwrap();
|
let user_id = syn::thing(&resource_id).unwrap();
|
||||||
assert_eq!(sess.sd, Some(Value::from(user_id)));
|
assert_eq!(sess.rd, Some(Value::from(user_id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1277,16 +1270,16 @@ mod tests {
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.sc, Some("test".to_string()));
|
assert_eq!(sess.ac, Some("token".to_string()));
|
||||||
assert_eq!(sess.au.id(), "token");
|
assert_eq!(sess.au.id(), resource_id);
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
let user_id = syn::thing(&resource_id).unwrap();
|
let user_id = syn::thing(&resource_id).unwrap();
|
||||||
assert_eq!(sess.sd, Some(Value::from(user_id)));
|
assert_eq!(sess.rd, Some(Value::from(user_id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_token_scope_custom_claims() {
|
async fn test_token_db_record_custom_claims() {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
let secret = "jwt_secret";
|
let secret = "jwt_secret";
|
||||||
|
@ -1295,7 +1288,15 @@ mod tests {
|
||||||
let ds = Datastore::new("memory").await.unwrap();
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
let sess = Session::owner().with_ns("test").with_db("test");
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
ds.execute(
|
ds.execute(
|
||||||
format!("DEFINE TOKEN token ON SCOPE test TYPE HS512 VALUE '{secret}';").as_str(),
|
format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS token ON DATABASE TYPE RECORD
|
||||||
|
WITH JWT ALGORITHM HS512 KEY '{secret}';
|
||||||
|
|
||||||
|
CREATE user:test;
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
&sess,
|
&sess,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -1315,10 +1316,10 @@ mod tests {
|
||||||
"iat": {now},
|
"iat": {now},
|
||||||
"nbf": {now},
|
"nbf": {now},
|
||||||
"exp": {later},
|
"exp": {later},
|
||||||
"tk": "token",
|
|
||||||
"ns": "test",
|
"ns": "test",
|
||||||
"db": "test",
|
"db": "test",
|
||||||
"sc": "test",
|
"ac": "token",
|
||||||
|
"id": "user:test",
|
||||||
|
|
||||||
"string_claim": "test",
|
"string_claim": "test",
|
||||||
"bool_claim": true,
|
"bool_claim": true,
|
||||||
|
@ -1351,9 +1352,9 @@ mod tests {
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.sc, Some("test".to_string()));
|
assert_eq!(sess.ac, Some("token".to_string()));
|
||||||
assert_eq!(sess.au.id(), "token");
|
assert_eq!(sess.au.id(), "user:test");
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
assert_eq!(sess.au.level().db(), Some("test"));
|
assert_eq!(sess.au.level().db(), Some("test"));
|
||||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||||
|
@ -1387,7 +1388,7 @@ mod tests {
|
||||||
|
|
||||||
#[cfg(feature = "jwks")]
|
#[cfg(feature = "jwks")]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_token_scope_jwks() {
|
async fn test_token_db_record_jwks() {
|
||||||
use crate::dbs::capabilities::{Capabilities, NetTarget, Targets};
|
use crate::dbs::capabilities::{Capabilities, NetTarget, Targets};
|
||||||
use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine};
|
use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine};
|
||||||
use jsonwebtoken::jwk::{Jwk, JwkSet};
|
use jsonwebtoken::jwk::{Jwk, JwkSet};
|
||||||
|
@ -1448,8 +1449,15 @@ mod tests {
|
||||||
|
|
||||||
let sess = Session::owner().with_ns("test").with_db("test");
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
ds.execute(
|
ds.execute(
|
||||||
format!("DEFINE TOKEN token ON SCOPE test TYPE JWKS VALUE '{server_url}/{jwks_path}';")
|
format!(
|
||||||
.as_str(),
|
r#"
|
||||||
|
DEFINE ACCESS token ON DATABASE TYPE RECORD
|
||||||
|
WITH JWT URL '{server_url}/{jwks_path}';
|
||||||
|
|
||||||
|
CREATE user:test;
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
&sess,
|
&sess,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -1470,16 +1478,16 @@ mod tests {
|
||||||
iat: Some(Utc::now().timestamp()),
|
iat: Some(Utc::now().timestamp()),
|
||||||
nbf: Some(Utc::now().timestamp()),
|
nbf: Some(Utc::now().timestamp()),
|
||||||
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
tk: Some("token".to_string()),
|
|
||||||
ns: Some("test".to_string()),
|
ns: Some("test".to_string()),
|
||||||
db: Some("test".to_string()),
|
db: Some("test".to_string()),
|
||||||
sc: Some("test".to_string()),
|
ac: Some("token".to_string()),
|
||||||
|
id: Some("user:test".to_string()),
|
||||||
..Claims::default()
|
..Claims::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Test without roles defined
|
// Test without roles defined
|
||||||
// Roles should be ignored in scope authentication
|
// Roles should be ignored in record access
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
// Prepare the claims object
|
// Prepare the claims object
|
||||||
|
@ -1494,9 +1502,9 @@ mod tests {
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
assert_eq!(sess.db, Some("test".to_string()));
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
assert_eq!(sess.sc, Some("test".to_string()));
|
assert_eq!(sess.ac, Some("token".to_string()));
|
||||||
assert_eq!(sess.au.id(), "token");
|
assert_eq!(sess.au.id(), "user:test");
|
||||||
assert!(sess.au.is_scope());
|
assert!(sess.au.is_record());
|
||||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
assert_eq!(sess.au.level().db(), Some("test"));
|
assert_eq!(sess.au.level().db(), Some("test"));
|
||||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
|
/// Stores a DEFINE ACCESS ON DATABASE config definition
|
||||||
use crate::key::error::KeyCategory;
|
use crate::key::error::KeyCategory;
|
||||||
use crate::key::key_req::KeyRequirements;
|
use crate::key::key_req::KeyRequirements;
|
||||||
/// Stores a DEFINE TOKEN ON DATABASE config definition
|
|
||||||
use derive::Key;
|
use derive::Key;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Tk<'a> {
|
pub struct Ac<'a> {
|
||||||
__: u8,
|
__: u8,
|
||||||
_a: u8,
|
_a: u8,
|
||||||
pub ns: &'a str,
|
pub ns: &'a str,
|
||||||
|
@ -15,33 +15,33 @@ pub struct Tk<'a> {
|
||||||
_c: u8,
|
_c: u8,
|
||||||
_d: u8,
|
_d: u8,
|
||||||
_e: u8,
|
_e: u8,
|
||||||
pub tk: &'a str,
|
pub ac: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new<'a>(ns: &'a str, db: &'a str, tk: &'a str) -> Tk<'a> {
|
pub fn new<'a>(ns: &'a str, db: &'a str, ac: &'a str) -> Ac<'a> {
|
||||||
Tk::new(ns, db, tk)
|
Ac::new(ns, db, ac)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prefix(ns: &str, db: &str) -> Vec<u8> {
|
pub fn prefix(ns: &str, db: &str) -> Vec<u8> {
|
||||||
let mut k = super::all::new(ns, db).encode().unwrap();
|
let mut k = super::all::new(ns, db).encode().unwrap();
|
||||||
k.extend_from_slice(&[b'!', b't', b'k', 0x00]);
|
k.extend_from_slice(&[b'!', b'a', b'c', 0x00]);
|
||||||
k
|
k
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn suffix(ns: &str, db: &str) -> Vec<u8> {
|
pub fn suffix(ns: &str, db: &str) -> Vec<u8> {
|
||||||
let mut k = super::all::new(ns, db).encode().unwrap();
|
let mut k = super::all::new(ns, db).encode().unwrap();
|
||||||
k.extend_from_slice(&[b'!', b't', b'k', 0xff]);
|
k.extend_from_slice(&[b'!', b'a', b'c', 0xff]);
|
||||||
k
|
k
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyRequirements for Tk<'_> {
|
impl KeyRequirements for Ac<'_> {
|
||||||
fn key_category(&self) -> KeyCategory {
|
fn key_category(&self) -> KeyCategory {
|
||||||
KeyCategory::DatabaseToken
|
KeyCategory::DatabaseAccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Tk<'a> {
|
impl<'a> Ac<'a> {
|
||||||
pub fn new(ns: &'a str, db: &'a str, tk: &'a str) -> Self {
|
pub fn new(ns: &'a str, db: &'a str, ac: &'a str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
__: b'/',
|
__: b'/',
|
||||||
_a: b'*',
|
_a: b'*',
|
||||||
|
@ -49,9 +49,9 @@ impl<'a> Tk<'a> {
|
||||||
_b: b'*',
|
_b: b'*',
|
||||||
db,
|
db,
|
||||||
_c: b'!',
|
_c: b'!',
|
||||||
_d: b't',
|
_d: b'a',
|
||||||
_e: b'k',
|
_e: b'c',
|
||||||
tk,
|
ac,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,27 +62,27 @@ mod tests {
|
||||||
fn key() {
|
fn key() {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let val = Tk::new(
|
let val = Ac::new(
|
||||||
"testns",
|
"testns",
|
||||||
"testdb",
|
"testdb",
|
||||||
"testtk",
|
"testac",
|
||||||
);
|
);
|
||||||
let enc = Tk::encode(&val).unwrap();
|
let enc = Ac::encode(&val).unwrap();
|
||||||
assert_eq!(enc, b"/*testns\x00*testdb\x00!tktesttk\x00");
|
assert_eq!(enc, b"/*testns\x00*testdb\x00!actestac\x00");
|
||||||
|
|
||||||
let dec = Tk::decode(&enc).unwrap();
|
let dec = Ac::decode(&enc).unwrap();
|
||||||
assert_eq!(val, dec);
|
assert_eq!(val, dec);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_prefix() {
|
fn test_prefix() {
|
||||||
let val = super::prefix("testns", "testdb");
|
let val = super::prefix("testns", "testdb");
|
||||||
assert_eq!(val, b"/*testns\0*testdb\0!tk\0");
|
assert_eq!(val, b"/*testns\0*testdb\0!ac\0");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_suffix() {
|
fn test_suffix() {
|
||||||
let val = super::suffix("testns", "testdb");
|
let val = super::suffix("testns", "testdb");
|
||||||
assert_eq!(val, b"/*testns\0*testdb\0!tk\xff");
|
assert_eq!(val, b"/*testns\0*testdb\0!ac\xff");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
|
pub mod ac;
|
||||||
pub mod all;
|
pub mod all;
|
||||||
pub mod az;
|
pub mod az;
|
||||||
pub mod fc;
|
pub mod fc;
|
||||||
pub mod ml;
|
pub mod ml;
|
||||||
pub mod pa;
|
pub mod pa;
|
||||||
pub mod sc;
|
|
||||||
pub mod tb;
|
pub mod tb;
|
||||||
pub mod ti;
|
pub mod ti;
|
||||||
pub mod tk;
|
|
||||||
pub mod ts;
|
pub mod ts;
|
||||||
pub mod us;
|
pub mod us;
|
||||||
pub mod vs;
|
pub mod vs;
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
//! Stores a DEFINE SCOPE config definition
|
|
||||||
use crate::key::error::KeyCategory;
|
|
||||||
use crate::key::key_req::KeyRequirements;
|
|
||||||
use derive::Key;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Sc<'a> {
|
|
||||||
__: u8,
|
|
||||||
_a: u8,
|
|
||||||
pub ns: &'a str,
|
|
||||||
_b: u8,
|
|
||||||
pub db: &'a str,
|
|
||||||
_c: u8,
|
|
||||||
_d: u8,
|
|
||||||
_e: u8,
|
|
||||||
pub sc: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new<'a>(ns: &'a str, db: &'a str, sc: &'a str) -> Sc<'a> {
|
|
||||||
Sc::new(ns, db, sc)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prefix(ns: &str, db: &str) -> Vec<u8> {
|
|
||||||
let mut k = super::all::new(ns, db).encode().unwrap();
|
|
||||||
k.extend_from_slice(&[b'!', b's', b'c', 0x00]);
|
|
||||||
k
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn suffix(ns: &str, db: &str) -> Vec<u8> {
|
|
||||||
let mut k = super::all::new(ns, db).encode().unwrap();
|
|
||||||
k.extend_from_slice(&[b'!', b's', b'c', 0xff]);
|
|
||||||
k
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyRequirements for Sc<'_> {
|
|
||||||
fn key_category(&self) -> KeyCategory {
|
|
||||||
KeyCategory::DatabaseScope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Sc<'a> {
|
|
||||||
pub fn new(ns: &'a str, db: &'a str, sc: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
__: b'/',
|
|
||||||
_a: b'*',
|
|
||||||
ns,
|
|
||||||
_b: b'*',
|
|
||||||
db,
|
|
||||||
_c: b'!',
|
|
||||||
_d: b's',
|
|
||||||
_e: b'c',
|
|
||||||
sc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn key() {
|
|
||||||
use super::*;
|
|
||||||
#[rustfmt::skip]
|
|
||||||
let val = Sc::new(
|
|
||||||
"testns",
|
|
||||||
"testdb",
|
|
||||||
"testsc",
|
|
||||||
);
|
|
||||||
let enc = Sc::encode(&val).unwrap();
|
|
||||||
assert_eq!(enc, b"/*testns\0*testdb\0!sctestsc\0");
|
|
||||||
|
|
||||||
let dec = Sc::decode(&enc).unwrap();
|
|
||||||
assert_eq!(val, dec);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,6 +8,8 @@ pub enum KeyCategory {
|
||||||
Unknown,
|
Unknown,
|
||||||
/// crate::key::root::all /
|
/// crate::key::root::all /
|
||||||
Root,
|
Root,
|
||||||
|
/// crate::key::root::ac /!ac{ac}
|
||||||
|
Access,
|
||||||
/// crate::key::root::hb /!hb{ts}/{nd}
|
/// crate::key::root::hb /!hb{ts}/{nd}
|
||||||
Heartbeat,
|
Heartbeat,
|
||||||
/// crate::key::root::nd /!nd{nd}
|
/// crate::key::root::nd /!nd{nd}
|
||||||
|
@ -32,13 +34,15 @@ pub enum KeyCategory {
|
||||||
DatabaseIdentifier,
|
DatabaseIdentifier,
|
||||||
/// crate::key::namespace::lg /*{ns}!lg{lg}
|
/// crate::key::namespace::lg /*{ns}!lg{lg}
|
||||||
DatabaseLogAlias,
|
DatabaseLogAlias,
|
||||||
/// crate::key::namespace::tk /*{ns}!tk{tk}
|
/// crate::key::namespace::ac /*{ns}!ac{ac}
|
||||||
NamespaceToken,
|
NamespaceAccess,
|
||||||
/// crate::key::namespace::us /*{ns}!us{us}
|
/// crate::key::namespace::us /*{ns}!us{us}
|
||||||
NamespaceUser,
|
NamespaceUser,
|
||||||
///
|
///
|
||||||
/// crate::key::database::all /*{ns}*{db}
|
/// crate::key::database::all /*{ns}*{db}
|
||||||
DatabaseRoot,
|
DatabaseRoot,
|
||||||
|
/// crate::key::database::ac /*{ns}*{db}!ac{ac}
|
||||||
|
DatabaseAccess,
|
||||||
/// crate::key::database::az /*{ns}*{db}!az{az}
|
/// crate::key::database::az /*{ns}*{db}!az{az}
|
||||||
DatabaseAnalyzer,
|
DatabaseAnalyzer,
|
||||||
/// crate::key::database::fc /*{ns}*{db}!fn{fc}
|
/// crate::key::database::fc /*{ns}*{db}!fn{fc}
|
||||||
|
@ -49,14 +53,10 @@ pub enum KeyCategory {
|
||||||
DatabaseModel,
|
DatabaseModel,
|
||||||
/// crate::key::database::pa /*{ns}*{db}!pa{pa}
|
/// crate::key::database::pa /*{ns}*{db}!pa{pa}
|
||||||
DatabaseParameter,
|
DatabaseParameter,
|
||||||
/// crate::key::database::sc /*{ns}*{db}!sc{sc}
|
|
||||||
DatabaseScope,
|
|
||||||
/// crate::key::database::tb /*{ns}*{db}!tb{tb}
|
/// crate::key::database::tb /*{ns}*{db}!tb{tb}
|
||||||
DatabaseTable,
|
DatabaseTable,
|
||||||
/// crate::key::database::ti /+{ns id}*{db id}!ti
|
/// crate::key::database::ti /+{ns id}*{db id}!ti
|
||||||
DatabaseTableIdentifier,
|
DatabaseTableIdentifier,
|
||||||
/// crate::key::database::tk /*{ns}*{db}!tk{tk}
|
|
||||||
DatabaseToken,
|
|
||||||
/// crate::key::database::ts /*{ns}*{db}!ts{ts}
|
/// crate::key::database::ts /*{ns}*{db}!ts{ts}
|
||||||
DatabaseTimestamp,
|
DatabaseTimestamp,
|
||||||
/// crate::key::database::us /*{ns}*{db}!us{us}
|
/// crate::key::database::us /*{ns}*{db}!us{us}
|
||||||
|
@ -64,11 +64,6 @@ pub enum KeyCategory {
|
||||||
/// crate::key::database::vs /*{ns}*{db}!vs
|
/// crate::key::database::vs /*{ns}*{db}!vs
|
||||||
DatabaseVersionstamp,
|
DatabaseVersionstamp,
|
||||||
///
|
///
|
||||||
/// crate::key::scope::all /*{ns}*{db}±{sc}
|
|
||||||
ScopeRoot,
|
|
||||||
/// crate::key::scope::tk /*{ns}*{db}±{sc}!tk{tk}
|
|
||||||
ScopeToken,
|
|
||||||
///
|
|
||||||
/// crate::key::table::all /*{ns}*{db}*{tb}
|
/// crate::key::table::all /*{ns}*{db}*{tb}
|
||||||
TableRoot,
|
TableRoot,
|
||||||
/// crate::key::table::ev /*{ns}*{db}*{tb}!ev{ev}
|
/// crate::key::table::ev /*{ns}*{db}*{tb}!ev{ev}
|
||||||
|
@ -124,6 +119,7 @@ impl Display for KeyCategory {
|
||||||
let name = match self {
|
let name = match self {
|
||||||
KeyCategory::Unknown => "Unknown",
|
KeyCategory::Unknown => "Unknown",
|
||||||
KeyCategory::Root => "Root",
|
KeyCategory::Root => "Root",
|
||||||
|
KeyCategory::Access => "Access",
|
||||||
KeyCategory::Heartbeat => "Heartbeat",
|
KeyCategory::Heartbeat => "Heartbeat",
|
||||||
KeyCategory::Node => "Node",
|
KeyCategory::Node => "Node",
|
||||||
KeyCategory::NamespaceIdentifier => "NamespaceIdentifier",
|
KeyCategory::NamespaceIdentifier => "NamespaceIdentifier",
|
||||||
|
@ -135,23 +131,20 @@ impl Display for KeyCategory {
|
||||||
KeyCategory::DatabaseAlias => "DatabaseAlias",
|
KeyCategory::DatabaseAlias => "DatabaseAlias",
|
||||||
KeyCategory::DatabaseIdentifier => "DatabaseIdentifier",
|
KeyCategory::DatabaseIdentifier => "DatabaseIdentifier",
|
||||||
KeyCategory::DatabaseLogAlias => "DatabaseLogAlias",
|
KeyCategory::DatabaseLogAlias => "DatabaseLogAlias",
|
||||||
KeyCategory::NamespaceToken => "NamespaceToken",
|
KeyCategory::NamespaceAccess => "NamespaceAccess",
|
||||||
KeyCategory::NamespaceUser => "NamespaceUser",
|
KeyCategory::NamespaceUser => "NamespaceUser",
|
||||||
KeyCategory::DatabaseRoot => "DatabaseRoot",
|
KeyCategory::DatabaseRoot => "DatabaseRoot",
|
||||||
|
KeyCategory::DatabaseAccess => "DatabaseAccess",
|
||||||
KeyCategory::DatabaseAnalyzer => "DatabaseAnalyzer",
|
KeyCategory::DatabaseAnalyzer => "DatabaseAnalyzer",
|
||||||
KeyCategory::DatabaseFunction => "DatabaseFunction",
|
KeyCategory::DatabaseFunction => "DatabaseFunction",
|
||||||
KeyCategory::DatabaseLog => "DatabaseLog",
|
KeyCategory::DatabaseLog => "DatabaseLog",
|
||||||
KeyCategory::DatabaseModel => "DatabaseModel",
|
KeyCategory::DatabaseModel => "DatabaseModel",
|
||||||
KeyCategory::DatabaseParameter => "DatabaseParameter",
|
KeyCategory::DatabaseParameter => "DatabaseParameter",
|
||||||
KeyCategory::DatabaseScope => "DatabaseScope",
|
|
||||||
KeyCategory::DatabaseTable => "DatabaseTable",
|
KeyCategory::DatabaseTable => "DatabaseTable",
|
||||||
KeyCategory::DatabaseTableIdentifier => "DatabaseTableIdentifier",
|
KeyCategory::DatabaseTableIdentifier => "DatabaseTableIdentifier",
|
||||||
KeyCategory::DatabaseToken => "DatabaseToken",
|
|
||||||
KeyCategory::DatabaseTimestamp => "DatabaseTimestamp",
|
KeyCategory::DatabaseTimestamp => "DatabaseTimestamp",
|
||||||
KeyCategory::DatabaseUser => "DatabaseUser",
|
KeyCategory::DatabaseUser => "DatabaseUser",
|
||||||
KeyCategory::DatabaseVersionstamp => "DatabaseVersionstamp",
|
KeyCategory::DatabaseVersionstamp => "DatabaseVersionstamp",
|
||||||
KeyCategory::ScopeRoot => "ScopeRoot",
|
|
||||||
KeyCategory::ScopeToken => "ScopeToken",
|
|
||||||
KeyCategory::TableRoot => "TableRoot",
|
KeyCategory::TableRoot => "TableRoot",
|
||||||
KeyCategory::TableEvent => "TableEvent",
|
KeyCategory::TableEvent => "TableEvent",
|
||||||
KeyCategory::TableField => "TableField",
|
KeyCategory::TableField => "TableField",
|
||||||
|
|
|
@ -11,28 +11,24 @@
|
||||||
/// crate::key::node::lq /${nd}!lq{lq}{ns}{db}
|
/// crate::key::node::lq /${nd}!lq{lq}{ns}{db}
|
||||||
///
|
///
|
||||||
/// crate::key::namespace::all /*{ns}
|
/// crate::key::namespace::all /*{ns}
|
||||||
|
/// crate::key::namespace::ac /*{ns}!ac{ac}
|
||||||
/// crate::key::namespace::db /*{ns}!db{db}
|
/// crate::key::namespace::db /*{ns}!db{db}
|
||||||
/// crate::key::namespace::di /+{ns id}!di
|
/// crate::key::namespace::di /+{ns id}!di
|
||||||
/// crate::key::namespace::lg /*{ns}!lg{lg}
|
/// crate::key::namespace::lg /*{ns}!lg{lg}
|
||||||
/// crate::key::namespace::tk /*{ns}!tk{tk}
|
|
||||||
/// crate::key::namespace::us /*{ns}!us{us}
|
/// crate::key::namespace::us /*{ns}!us{us}
|
||||||
///
|
///
|
||||||
/// crate::key::database::all /*{ns}*{db}
|
/// crate::key::database::all /*{ns}*{db}
|
||||||
|
/// crate::key::database::ac /*{ns}*{db}!ac{ac}
|
||||||
/// crate::key::database::az /*{ns}*{db}!az{az}
|
/// crate::key::database::az /*{ns}*{db}!az{az}
|
||||||
/// crate::key::database::fc /*{ns}*{db}!fn{fc}
|
/// crate::key::database::fc /*{ns}*{db}!fn{fc}
|
||||||
/// crate::key::database::lg /*{ns}*{db}!lg{lg}
|
/// crate::key::database::lg /*{ns}*{db}!lg{lg}
|
||||||
/// crate::key::database::pa /*{ns}*{db}!pa{pa}
|
/// crate::key::database::pa /*{ns}*{db}!pa{pa}
|
||||||
/// crate::key::database::sc /*{ns}*{db}!sc{sc}
|
|
||||||
/// crate::key::database::tb /*{ns}*{db}!tb{tb}
|
/// crate::key::database::tb /*{ns}*{db}!tb{tb}
|
||||||
/// crate::key::database::ti /+{ns id}*{db id}!ti
|
/// crate::key::database::ti /+{ns id}*{db id}!ti
|
||||||
/// crate::key::database::tk /*{ns}*{db}!tk{tk}
|
|
||||||
/// crate::key::database::ts /*{ns}*{db}!ts{ts}
|
/// crate::key::database::ts /*{ns}*{db}!ts{ts}
|
||||||
/// crate::key::database::us /*{ns}*{db}!us{us}
|
/// crate::key::database::us /*{ns}*{db}!us{us}
|
||||||
/// crate::key::database::vs /*{ns}*{db}!vs
|
/// crate::key::database::vs /*{ns}*{db}!vs
|
||||||
///
|
///
|
||||||
/// crate::key::scope::all /*{ns}*{db}±{sc}
|
|
||||||
/// crate::key::scope::tk /*{ns}*{db}±{sc}!tk{tk}
|
|
||||||
///
|
|
||||||
/// crate::key::table::all /*{ns}*{db}*{tb}
|
/// crate::key::table::all /*{ns}*{db}*{tb}
|
||||||
/// crate::key::table::ev /*{ns}*{db}*{tb}!ev{ev}
|
/// crate::key::table::ev /*{ns}*{db}*{tb}!ev{ev}
|
||||||
/// crate::key::table::fd /*{ns}*{db}*{tb}!fd{fd}
|
/// crate::key::table::fd /*{ns}*{db}*{tb}!fd{fd}
|
||||||
|
@ -70,6 +66,5 @@ pub(crate) mod key_req;
|
||||||
pub mod namespace;
|
pub mod namespace;
|
||||||
pub mod node;
|
pub mod node;
|
||||||
pub mod root;
|
pub mod root;
|
||||||
pub mod scope;
|
|
||||||
pub mod table;
|
pub mod table;
|
||||||
pub mod thing;
|
pub mod thing;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Stores a DEFINE TOKEN ON NAMESPACE config definition
|
//! Stores a DEFINE ACCESS ON NAMESPACE config definition
|
||||||
use crate::key::error::KeyCategory;
|
use crate::key::error::KeyCategory;
|
||||||
use crate::key::key_req::KeyRequirements;
|
use crate::key::key_req::KeyRequirements;
|
||||||
use derive::Key;
|
use derive::Key;
|
||||||
|
@ -6,48 +6,48 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Tk<'a> {
|
pub struct Ac<'a> {
|
||||||
__: u8,
|
__: u8,
|
||||||
_a: u8,
|
_a: u8,
|
||||||
pub ns: &'a str,
|
pub ns: &'a str,
|
||||||
_b: u8,
|
_b: u8,
|
||||||
_c: u8,
|
_c: u8,
|
||||||
_d: u8,
|
_d: u8,
|
||||||
pub tk: &'a str,
|
pub ac: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new<'a>(ns: &'a str, tk: &'a str) -> Tk<'a> {
|
pub fn new<'a>(ns: &'a str, ac: &'a str) -> Ac<'a> {
|
||||||
Tk::new(ns, tk)
|
Ac::new(ns, ac)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prefix(ns: &str) -> Vec<u8> {
|
pub fn prefix(ns: &str) -> Vec<u8> {
|
||||||
let mut k = super::all::new(ns).encode().unwrap();
|
let mut k = super::all::new(ns).encode().unwrap();
|
||||||
k.extend_from_slice(&[b'!', b't', b'k', 0x00]);
|
k.extend_from_slice(&[b'!', b'a', b'c', 0x00]);
|
||||||
k
|
k
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn suffix(ns: &str) -> Vec<u8> {
|
pub fn suffix(ns: &str) -> Vec<u8> {
|
||||||
let mut k = super::all::new(ns).encode().unwrap();
|
let mut k = super::all::new(ns).encode().unwrap();
|
||||||
k.extend_from_slice(&[b'!', b't', b'k', 0xff]);
|
k.extend_from_slice(&[b'!', b'a', b'c', 0xff]);
|
||||||
k
|
k
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyRequirements for Tk<'_> {
|
impl KeyRequirements for Ac<'_> {
|
||||||
fn key_category(&self) -> KeyCategory {
|
fn key_category(&self) -> KeyCategory {
|
||||||
KeyCategory::NamespaceToken
|
KeyCategory::NamespaceAccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Tk<'a> {
|
impl<'a> Ac<'a> {
|
||||||
pub fn new(ns: &'a str, tk: &'a str) -> Self {
|
pub fn new(ns: &'a str, ac: &'a str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
__: b'/',
|
__: b'/',
|
||||||
_a: b'*',
|
_a: b'*',
|
||||||
ns,
|
ns,
|
||||||
_b: b'!',
|
_b: b'!',
|
||||||
_c: b't',
|
_c: b'a',
|
||||||
_d: b'k',
|
_d: b'c',
|
||||||
tk,
|
ac,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,13 +58,13 @@ mod tests {
|
||||||
fn key() {
|
fn key() {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let val = Tk::new(
|
let val = Ac::new(
|
||||||
"testns",
|
"testns",
|
||||||
"testtk",
|
"testac",
|
||||||
);
|
);
|
||||||
let enc = Tk::encode(&val).unwrap();
|
let enc = Ac::encode(&val).unwrap();
|
||||||
assert_eq!(enc, b"/*testns\0!tktesttk\0");
|
assert_eq!(enc, b"/*testns\0!actestac\0");
|
||||||
let dec = Tk::decode(&enc).unwrap();
|
let dec = Ac::decode(&enc).unwrap();
|
||||||
assert_eq!(val, dec);
|
assert_eq!(val, dec);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
|
pub mod ac;
|
||||||
pub mod all;
|
pub mod all;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod di;
|
pub mod di;
|
||||||
pub mod tk;
|
|
||||||
pub mod us;
|
pub mod us;
|
||||||
|
|
74
core/src/key/root/ac.rs
Normal file
74
core/src/key/root/ac.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use crate::key::error::KeyCategory;
|
||||||
|
use crate::key::key_req::KeyRequirements;
|
||||||
|
use derive::Key;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Ac<'a> {
|
||||||
|
__: u8,
|
||||||
|
_a: u8,
|
||||||
|
_b: u8,
|
||||||
|
_c: u8,
|
||||||
|
pub ac: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(ac: &str) -> Ac<'_> {
|
||||||
|
Ac::new(ac)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prefix() -> Vec<u8> {
|
||||||
|
let mut k = super::all::new().encode().unwrap();
|
||||||
|
k.extend_from_slice(&[b'!', b'a', b'c', 0x00]);
|
||||||
|
k
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn suffix() -> Vec<u8> {
|
||||||
|
let mut k = super::all::new().encode().unwrap();
|
||||||
|
k.extend_from_slice(&[b'!', b'a', b'c', 0xff]);
|
||||||
|
k
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyRequirements for Ac<'_> {
|
||||||
|
fn key_category(&self) -> KeyCategory {
|
||||||
|
KeyCategory::Access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Ac<'a> {
|
||||||
|
pub fn new(ac: &'a str) -> Self {
|
||||||
|
Self {
|
||||||
|
__: b'/',
|
||||||
|
_a: b'!',
|
||||||
|
_b: b'a',
|
||||||
|
_c: b'c',
|
||||||
|
ac,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn key() {
|
||||||
|
use super::*;
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let val = Ac::new("testac");
|
||||||
|
let enc = Ac::encode(&val).unwrap();
|
||||||
|
assert_eq!(enc, b"/!actestac\x00");
|
||||||
|
let dec = Ac::decode(&enc).unwrap();
|
||||||
|
assert_eq!(val, dec);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prefix() {
|
||||||
|
let val = super::prefix();
|
||||||
|
assert_eq!(val, b"/!ac\0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_suffix() {
|
||||||
|
let val = super::suffix();
|
||||||
|
assert_eq!(val, b"/!ac\xff");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod ac;
|
||||||
pub mod all;
|
pub mod all;
|
||||||
pub mod hb;
|
pub mod hb;
|
||||||
pub mod nd;
|
pub mod nd;
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
//! Stores the key prefix for all keys under a scope
|
|
||||||
use crate::key::error::KeyCategory;
|
|
||||||
use crate::key::key_req::KeyRequirements;
|
|
||||||
use derive::Key;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Scope<'a> {
|
|
||||||
__: u8,
|
|
||||||
_a: u8,
|
|
||||||
pub ns: &'a str,
|
|
||||||
_b: u8,
|
|
||||||
pub db: &'a str,
|
|
||||||
_c: u8,
|
|
||||||
pub sc: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new<'a>(ns: &'a str, db: &'a str, sc: &'a str) -> Scope<'a> {
|
|
||||||
Scope::new(ns, db, sc)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyRequirements for Scope<'_> {
|
|
||||||
fn key_category(&self) -> KeyCategory {
|
|
||||||
KeyCategory::ScopeRoot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Scope<'a> {
|
|
||||||
pub fn new(ns: &'a str, db: &'a str, sc: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
__: b'/',
|
|
||||||
_a: b'*',
|
|
||||||
ns,
|
|
||||||
_b: b'*',
|
|
||||||
db,
|
|
||||||
_c: super::CHAR,
|
|
||||||
sc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn key() {
|
|
||||||
use super::*;
|
|
||||||
#[rustfmt::skip]
|
|
||||||
let val = Scope::new(
|
|
||||||
"testns",
|
|
||||||
"testdb",
|
|
||||||
"testsc",
|
|
||||||
);
|
|
||||||
let enc = Scope::encode(&val).unwrap();
|
|
||||||
assert_eq!(enc, b"/*testns\0*testdb\0\xb1testsc\0");
|
|
||||||
|
|
||||||
let dec = Scope::decode(&enc).unwrap();
|
|
||||||
assert_eq!(val, dec);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
pub mod all;
|
|
||||||
pub mod tk;
|
|
||||||
|
|
||||||
const CHAR: u8 = 0xb1; // ±
|
|
|
@ -1,81 +0,0 @@
|
||||||
//! Stores a DEFINE TOKEN ON SCOPE config definition
|
|
||||||
use crate::key::error::KeyCategory;
|
|
||||||
use crate::key::key_req::KeyRequirements;
|
|
||||||
use derive::Key;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Tk<'a> {
|
|
||||||
__: u8,
|
|
||||||
_a: u8,
|
|
||||||
pub ns: &'a str,
|
|
||||||
_b: u8,
|
|
||||||
pub db: &'a str,
|
|
||||||
_c: u8,
|
|
||||||
pub sc: &'a str,
|
|
||||||
_d: u8,
|
|
||||||
_e: u8,
|
|
||||||
_f: u8,
|
|
||||||
pub tk: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new<'a>(ns: &'a str, db: &'a str, sc: &'a str, tk: &'a str) -> Tk<'a> {
|
|
||||||
Tk::new(ns, db, sc, tk)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prefix(ns: &str, db: &str, sc: &str) -> Vec<u8> {
|
|
||||||
let mut k = super::all::new(ns, db, sc).encode().unwrap();
|
|
||||||
k.extend_from_slice(&[b'!', b't', b'k', 0x00]);
|
|
||||||
k
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn suffix(ns: &str, db: &str, sc: &str) -> Vec<u8> {
|
|
||||||
let mut k = super::all::new(ns, db, sc).encode().unwrap();
|
|
||||||
k.extend_from_slice(&[b'!', b't', b'k', 0xff]);
|
|
||||||
k
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyRequirements for Tk<'_> {
|
|
||||||
fn key_category(&self) -> KeyCategory {
|
|
||||||
KeyCategory::ScopeToken
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Tk<'a> {
|
|
||||||
pub fn new(ns: &'a str, db: &'a str, sc: &'a str, tk: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
__: b'/',
|
|
||||||
_a: b'*',
|
|
||||||
ns,
|
|
||||||
_b: b'*',
|
|
||||||
db,
|
|
||||||
_c: super::CHAR,
|
|
||||||
sc,
|
|
||||||
_d: b'!',
|
|
||||||
_e: b't',
|
|
||||||
_f: b'k',
|
|
||||||
tk,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn key() {
|
|
||||||
use super::*;
|
|
||||||
#[rustfmt::skip]
|
|
||||||
let val = Tk::new(
|
|
||||||
"testns",
|
|
||||||
"testdb",
|
|
||||||
"testsc",
|
|
||||||
"testtk",
|
|
||||||
);
|
|
||||||
let enc = Tk::encode(&val).unwrap();
|
|
||||||
assert_eq!(enc, b"/*testns\0*testdb\0\xb1testsc\0!tktesttk\0");
|
|
||||||
|
|
||||||
let dec = Tk::decode(&enc).unwrap();
|
|
||||||
assert_eq!(val, dec);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::idg::u32::U32;
|
use crate::idg::u32::U32;
|
||||||
use crate::kvs::kv::Key;
|
use crate::kvs::kv::Key;
|
||||||
|
use crate::sql::statements::DefineAccessStatement;
|
||||||
use crate::sql::statements::DefineAnalyzerStatement;
|
use crate::sql::statements::DefineAnalyzerStatement;
|
||||||
use crate::sql::statements::DefineDatabaseStatement;
|
use crate::sql::statements::DefineDatabaseStatement;
|
||||||
use crate::sql::statements::DefineEventStatement;
|
use crate::sql::statements::DefineEventStatement;
|
||||||
|
@ -9,9 +10,7 @@ use crate::sql::statements::DefineIndexStatement;
|
||||||
use crate::sql::statements::DefineModelStatement;
|
use crate::sql::statements::DefineModelStatement;
|
||||||
use crate::sql::statements::DefineNamespaceStatement;
|
use crate::sql::statements::DefineNamespaceStatement;
|
||||||
use crate::sql::statements::DefineParamStatement;
|
use crate::sql::statements::DefineParamStatement;
|
||||||
use crate::sql::statements::DefineScopeStatement;
|
|
||||||
use crate::sql::statements::DefineTableStatement;
|
use crate::sql::statements::DefineTableStatement;
|
||||||
use crate::sql::statements::DefineTokenStatement;
|
|
||||||
use crate::sql::statements::DefineUserStatement;
|
use crate::sql::statements::DefineUserStatement;
|
||||||
use crate::sql::statements::LiveStatement;
|
use crate::sql::statements::LiveStatement;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -31,7 +30,7 @@ pub enum Entry {
|
||||||
// Multi definitions
|
// Multi definitions
|
||||||
Azs(Arc<[DefineAnalyzerStatement]>),
|
Azs(Arc<[DefineAnalyzerStatement]>),
|
||||||
Dbs(Arc<[DefineDatabaseStatement]>),
|
Dbs(Arc<[DefineDatabaseStatement]>),
|
||||||
Dts(Arc<[DefineTokenStatement]>),
|
Das(Arc<[DefineAccessStatement]>),
|
||||||
Dus(Arc<[DefineUserStatement]>),
|
Dus(Arc<[DefineUserStatement]>),
|
||||||
Evs(Arc<[DefineEventStatement]>),
|
Evs(Arc<[DefineEventStatement]>),
|
||||||
Fcs(Arc<[DefineFunctionStatement]>),
|
Fcs(Arc<[DefineFunctionStatement]>),
|
||||||
|
@ -41,11 +40,9 @@ pub enum Entry {
|
||||||
Lvs(Arc<[LiveStatement]>),
|
Lvs(Arc<[LiveStatement]>),
|
||||||
Mls(Arc<[DefineModelStatement]>),
|
Mls(Arc<[DefineModelStatement]>),
|
||||||
Nss(Arc<[DefineNamespaceStatement]>),
|
Nss(Arc<[DefineNamespaceStatement]>),
|
||||||
Nts(Arc<[DefineTokenStatement]>),
|
Nas(Arc<[DefineAccessStatement]>),
|
||||||
Nus(Arc<[DefineUserStatement]>),
|
Nus(Arc<[DefineUserStatement]>),
|
||||||
Pas(Arc<[DefineParamStatement]>),
|
Pas(Arc<[DefineParamStatement]>),
|
||||||
Scs(Arc<[DefineScopeStatement]>),
|
|
||||||
Sts(Arc<[DefineTokenStatement]>),
|
|
||||||
Tbs(Arc<[DefineTableStatement]>),
|
Tbs(Arc<[DefineTableStatement]>),
|
||||||
// Sequences
|
// Sequences
|
||||||
Seq(U32),
|
Seq(U32),
|
||||||
|
|
|
@ -1311,7 +1311,7 @@ impl Datastore {
|
||||||
/// Evaluates a SQL [`Value`] without checking authenticating config
|
/// Evaluates a SQL [`Value`] without checking authenticating config
|
||||||
/// This is used in very specific cases, where we do not need to check
|
/// This is used in very specific cases, where we do not need to check
|
||||||
/// whether authentication is enabled, or guest access is disabled.
|
/// whether authentication is enabled, or guest access is disabled.
|
||||||
/// For example, this is used when processing a SCOPE SIGNUP or SCOPE
|
/// For example, this is used when processing a record access SIGNUP or
|
||||||
/// SIGNIN clause, which still needs to work without guest access.
|
/// SIGNIN clause, which still needs to work without guest access.
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
|
|
|
@ -214,7 +214,7 @@ mod test_check_lqs_and_send_notifications {
|
||||||
use crate::iam::{Auth, Role};
|
use crate::iam::{Auth, Role};
|
||||||
use crate::kvs::lq_v2_doc::construct_document;
|
use crate::kvs::lq_v2_doc::construct_document;
|
||||||
use crate::kvs::{Datastore, LockType, TransactionType};
|
use crate::kvs::{Datastore, LockType, TransactionType};
|
||||||
use crate::sql::paths::{OBJ_PATH_AUTH, OBJ_PATH_SCOPE, OBJ_PATH_TOKEN};
|
use crate::sql::paths::{OBJ_PATH_ACCESS, OBJ_PATH_AUTH, OBJ_PATH_TOKEN};
|
||||||
use crate::sql::statements::{CreateStatement, DeleteStatement, LiveStatement};
|
use crate::sql::statements::{CreateStatement, DeleteStatement, LiveStatement};
|
||||||
use crate::sql::{Fields, Object, Strand, Table, Thing, Uuid, Value, Values};
|
use crate::sql::{Fields, Object, Strand, Table, Thing, Uuid, Value, Values};
|
||||||
|
|
||||||
|
@ -379,8 +379,8 @@ mod test_check_lqs_and_send_notifications {
|
||||||
fn a_live_query_statement() -> LiveStatement {
|
fn a_live_query_statement() -> LiveStatement {
|
||||||
let mut stm = LiveStatement::new(Fields::all());
|
let mut stm = LiveStatement::new(Fields::all());
|
||||||
let mut session: BTreeMap<String, Value> = BTreeMap::new();
|
let mut session: BTreeMap<String, Value> = BTreeMap::new();
|
||||||
|
session.insert(OBJ_PATH_ACCESS.to_string(), Value::Strand(Strand::from("access")));
|
||||||
session.insert(OBJ_PATH_AUTH.to_string(), Value::Strand(Strand::from("auth")));
|
session.insert(OBJ_PATH_AUTH.to_string(), Value::Strand(Strand::from("auth")));
|
||||||
session.insert(OBJ_PATH_SCOPE.to_string(), Value::Strand(Strand::from("scope")));
|
|
||||||
session.insert(OBJ_PATH_TOKEN.to_string(), Value::Strand(Strand::from("token")));
|
session.insert(OBJ_PATH_TOKEN.to_string(), Value::Strand(Strand::from("token")));
|
||||||
let session = Value::Object(Object::from(session));
|
let session = Value::Object(Object::from(session));
|
||||||
stm.session = Some(session);
|
stm.session = Some(session);
|
||||||
|
|
|
@ -9,6 +9,7 @@ use futures::lock::Mutex;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use sql::permission::Permissions;
|
use sql::permission::Permissions;
|
||||||
|
use sql::statements::DefineAccessStatement;
|
||||||
use sql::statements::DefineAnalyzerStatement;
|
use sql::statements::DefineAnalyzerStatement;
|
||||||
use sql::statements::DefineDatabaseStatement;
|
use sql::statements::DefineDatabaseStatement;
|
||||||
use sql::statements::DefineEventStatement;
|
use sql::statements::DefineEventStatement;
|
||||||
|
@ -18,9 +19,7 @@ use sql::statements::DefineIndexStatement;
|
||||||
use sql::statements::DefineModelStatement;
|
use sql::statements::DefineModelStatement;
|
||||||
use sql::statements::DefineNamespaceStatement;
|
use sql::statements::DefineNamespaceStatement;
|
||||||
use sql::statements::DefineParamStatement;
|
use sql::statements::DefineParamStatement;
|
||||||
use sql::statements::DefineScopeStatement;
|
|
||||||
use sql::statements::DefineTableStatement;
|
use sql::statements::DefineTableStatement;
|
||||||
use sql::statements::DefineTokenStatement;
|
|
||||||
use sql::statements::DefineUserStatement;
|
use sql::statements::DefineUserStatement;
|
||||||
use sql::statements::LiveStatement;
|
use sql::statements::LiveStatement;
|
||||||
|
|
||||||
|
@ -1442,21 +1441,24 @@ impl Transaction {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve all namespace token definitions for a specific namespace.
|
/// Retrieve all namespace access method definitions.
|
||||||
pub async fn all_ns_tokens(&mut self, ns: &str) -> Result<Arc<[DefineTokenStatement]>, Error> {
|
pub async fn all_ns_accesses(
|
||||||
let key = crate::key::namespace::tk::prefix(ns);
|
&mut self,
|
||||||
|
ns: &str,
|
||||||
|
) -> Result<Arc<[DefineAccessStatement]>, Error> {
|
||||||
|
let key = crate::key::namespace::ac::prefix(ns);
|
||||||
Ok(if let Some(e) = self.cache.get(&key) {
|
Ok(if let Some(e) = self.cache.get(&key) {
|
||||||
if let Entry::Nts(v) = e {
|
if let Entry::Nas(v) = e {
|
||||||
v
|
v
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let beg = crate::key::namespace::tk::prefix(ns);
|
let beg = crate::key::namespace::ac::prefix(ns);
|
||||||
let end = crate::key::namespace::tk::suffix(ns);
|
let end = crate::key::namespace::ac::suffix(ns);
|
||||||
let val = self.getr(beg..end, u32::MAX).await?;
|
let val = self.getr(beg..end, u32::MAX).await?;
|
||||||
let val = val.convert().into();
|
let val = val.convert().into();
|
||||||
self.cache.set(key, Entry::Nts(Arc::clone(&val)));
|
self.cache.set(key, Entry::Nas(Arc::clone(&val)));
|
||||||
val
|
val
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1503,25 +1505,25 @@ impl Transaction {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve all database token definitions for a specific database.
|
/// Retrieve all database access method definitions.
|
||||||
pub async fn all_db_tokens(
|
pub async fn all_db_accesses(
|
||||||
&mut self,
|
&mut self,
|
||||||
ns: &str,
|
ns: &str,
|
||||||
db: &str,
|
db: &str,
|
||||||
) -> Result<Arc<[DefineTokenStatement]>, Error> {
|
) -> Result<Arc<[DefineAccessStatement]>, Error> {
|
||||||
let key = crate::key::database::tk::prefix(ns, db);
|
let key = crate::key::database::ac::prefix(ns, db);
|
||||||
Ok(if let Some(e) = self.cache.get(&key) {
|
Ok(if let Some(e) = self.cache.get(&key) {
|
||||||
if let Entry::Dts(v) = e {
|
if let Entry::Das(v) = e {
|
||||||
v
|
v
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let beg = crate::key::database::tk::prefix(ns, db);
|
let beg = crate::key::database::ac::prefix(ns, db);
|
||||||
let end = crate::key::database::tk::suffix(ns, db);
|
let end = crate::key::database::ac::suffix(ns, db);
|
||||||
let val = self.getr(beg..end, u32::MAX).await?;
|
let val = self.getr(beg..end, u32::MAX).await?;
|
||||||
let val = val.convert().into();
|
let val = val.convert().into();
|
||||||
self.cache.set(key, Entry::Dts(Arc::clone(&val)));
|
self.cache.set(key, Entry::Das(Arc::clone(&val)));
|
||||||
val
|
val
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1618,53 +1620,6 @@ impl Transaction {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve all scope definitions for a specific database.
|
|
||||||
pub async fn all_sc(
|
|
||||||
&mut self,
|
|
||||||
ns: &str,
|
|
||||||
db: &str,
|
|
||||||
) -> Result<Arc<[DefineScopeStatement]>, Error> {
|
|
||||||
let key = crate::key::database::sc::prefix(ns, db);
|
|
||||||
Ok(if let Some(e) = self.cache.get(&key) {
|
|
||||||
if let Entry::Scs(v) = e {
|
|
||||||
v
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let beg = crate::key::database::sc::prefix(ns, db);
|
|
||||||
let end = crate::key::database::sc::suffix(ns, db);
|
|
||||||
let val = self.getr(beg..end, u32::MAX).await?;
|
|
||||||
let val = val.convert().into();
|
|
||||||
self.cache.set(key, Entry::Scs(Arc::clone(&val)));
|
|
||||||
val
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve all scope token definitions for a scope.
|
|
||||||
pub async fn all_sc_tokens(
|
|
||||||
&mut self,
|
|
||||||
ns: &str,
|
|
||||||
db: &str,
|
|
||||||
sc: &str,
|
|
||||||
) -> Result<Arc<[DefineTokenStatement]>, Error> {
|
|
||||||
let key = crate::key::scope::tk::prefix(ns, db, sc);
|
|
||||||
Ok(if let Some(e) = self.cache.get(&key) {
|
|
||||||
if let Entry::Sts(v) = e {
|
|
||||||
v
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let beg = crate::key::scope::tk::prefix(ns, db, sc);
|
|
||||||
let end = crate::key::scope::tk::suffix(ns, db, sc);
|
|
||||||
let val = self.getr(beg..end, u32::MAX).await?;
|
|
||||||
let val = val.convert().into();
|
|
||||||
self.cache.set(key, Entry::Sts(Arc::clone(&val)));
|
|
||||||
val
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve all table definitions for a specific database.
|
/// Retrieve all table definitions for a specific database.
|
||||||
pub async fn all_tb(
|
pub async fn all_tb(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -1863,15 +1818,15 @@ impl Transaction {
|
||||||
Ok(val.into())
|
Ok(val.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve a specific namespace token definition.
|
/// Retrieve a specific namespace access method definition.
|
||||||
pub async fn get_ns_token(
|
pub async fn get_ns_access(
|
||||||
&mut self,
|
&mut self,
|
||||||
ns: &str,
|
ns: &str,
|
||||||
nt: &str,
|
ac: &str,
|
||||||
) -> Result<DefineTokenStatement, Error> {
|
) -> Result<DefineAccessStatement, Error> {
|
||||||
let key = crate::key::namespace::tk::new(ns, nt);
|
let key = crate::key::namespace::ac::new(ns, ac);
|
||||||
let val = self.get(key).await?.ok_or(Error::NtNotFound {
|
let val = self.get(key).await?.ok_or(Error::NaNotFound {
|
||||||
value: nt.to_owned(),
|
value: ac.to_owned(),
|
||||||
})?;
|
})?;
|
||||||
Ok(val.into())
|
Ok(val.into())
|
||||||
}
|
}
|
||||||
|
@ -1916,16 +1871,16 @@ impl Transaction {
|
||||||
Ok(val.into())
|
Ok(val.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve a specific database token definition.
|
/// Retrieve a specific database access method definition.
|
||||||
pub async fn get_db_token(
|
pub async fn get_db_access(
|
||||||
&mut self,
|
&mut self,
|
||||||
ns: &str,
|
ns: &str,
|
||||||
db: &str,
|
db: &str,
|
||||||
dt: &str,
|
ac: &str,
|
||||||
) -> Result<DefineTokenStatement, Error> {
|
) -> Result<DefineAccessStatement, Error> {
|
||||||
let key = crate::key::database::tk::new(ns, db, dt);
|
let key = crate::key::database::ac::new(ns, db, ac);
|
||||||
let val = self.get(key).await?.ok_or(Error::DtNotFound {
|
let val = self.get(key).await?.ok_or(Error::DaNotFound {
|
||||||
value: dt.to_owned(),
|
value: ac.to_owned(),
|
||||||
})?;
|
})?;
|
||||||
Ok(val.into())
|
Ok(val.into())
|
||||||
}
|
}
|
||||||
|
@ -1972,35 +1927,6 @@ impl Transaction {
|
||||||
Ok(val.into())
|
Ok(val.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve a specific scope definition.
|
|
||||||
pub async fn get_sc(
|
|
||||||
&mut self,
|
|
||||||
ns: &str,
|
|
||||||
db: &str,
|
|
||||||
sc: &str,
|
|
||||||
) -> Result<DefineScopeStatement, Error> {
|
|
||||||
let key = crate::key::database::sc::new(ns, db, sc);
|
|
||||||
let val = self.get(key).await?.ok_or(Error::ScNotFound {
|
|
||||||
value: sc.to_owned(),
|
|
||||||
})?;
|
|
||||||
Ok(val.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a specific scope token definition.
|
|
||||||
pub async fn get_sc_token(
|
|
||||||
&mut self,
|
|
||||||
ns: &str,
|
|
||||||
db: &str,
|
|
||||||
sc: &str,
|
|
||||||
st: &str,
|
|
||||||
) -> Result<DefineTokenStatement, Error> {
|
|
||||||
let key = crate::key::scope::tk::new(ns, db, sc, st);
|
|
||||||
let val = self.get(key).await?.ok_or(Error::StNotFound {
|
|
||||||
value: st.to_owned(),
|
|
||||||
})?;
|
|
||||||
Ok(val.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the table stored at the lq address
|
/// Return the table stored at the lq address
|
||||||
pub async fn get_lq(
|
pub async fn get_lq(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -2159,36 +2085,6 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a scope with a default configuration, only if we are in dynamic mode.
|
|
||||||
pub async fn add_sc(
|
|
||||||
&mut self,
|
|
||||||
ns: &str,
|
|
||||||
db: &str,
|
|
||||||
sc: &str,
|
|
||||||
strict: bool,
|
|
||||||
) -> Result<DefineScopeStatement, Error> {
|
|
||||||
match self.get_sc(ns, db, sc).await {
|
|
||||||
Err(Error::ScNotFound {
|
|
||||||
value,
|
|
||||||
}) => match strict {
|
|
||||||
false => {
|
|
||||||
let key = crate::key::database::sc::new(ns, db, sc);
|
|
||||||
let val = DefineScopeStatement {
|
|
||||||
name: sc.to_owned().into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
self.put(key.key_category(), key, &val).await?;
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
true => Err(Error::ScNotFound {
|
|
||||||
value,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
Err(e) => Err(e),
|
|
||||||
Ok(v) => Ok(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a table with a default configuration, only if we are in dynamic mode.
|
/// Add a table with a default configuration, only if we are in dynamic mode.
|
||||||
pub async fn add_tb(
|
pub async fn add_tb(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -2525,12 +2421,12 @@ impl Transaction {
|
||||||
chn.send(bytes!("")).await?;
|
chn.send(bytes!("")).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Output TOKENS
|
// Output ACCESSES
|
||||||
{
|
{
|
||||||
let dts = self.all_db_tokens(ns, db).await?;
|
let dts = self.all_db_accesses(ns, db).await?;
|
||||||
if !dts.is_empty() {
|
if !dts.is_empty() {
|
||||||
chn.send(bytes!("-- ------------------------------")).await?;
|
chn.send(bytes!("-- ------------------------------")).await?;
|
||||||
chn.send(bytes!("-- TOKENS")).await?;
|
chn.send(bytes!("-- ACCESSES")).await?;
|
||||||
chn.send(bytes!("-- ------------------------------")).await?;
|
chn.send(bytes!("-- ------------------------------")).await?;
|
||||||
chn.send(bytes!("")).await?;
|
chn.send(bytes!("")).await?;
|
||||||
for dt in dts.iter() {
|
for dt in dts.iter() {
|
||||||
|
@ -2581,31 +2477,6 @@ impl Transaction {
|
||||||
chn.send(bytes!("")).await?;
|
chn.send(bytes!("")).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Output SCOPES
|
|
||||||
{
|
|
||||||
let scs = self.all_sc(ns, db).await?;
|
|
||||||
if !scs.is_empty() {
|
|
||||||
chn.send(bytes!("-- ------------------------------")).await?;
|
|
||||||
chn.send(bytes!("-- SCOPES")).await?;
|
|
||||||
chn.send(bytes!("-- ------------------------------")).await?;
|
|
||||||
chn.send(bytes!("")).await?;
|
|
||||||
for sc in scs.iter() {
|
|
||||||
// Output SCOPE
|
|
||||||
chn.send(bytes!(format!("{sc};"))).await?;
|
|
||||||
// Output TOKENS
|
|
||||||
{
|
|
||||||
let sts = self.all_sc_tokens(ns, db, &sc.name).await?;
|
|
||||||
if !sts.is_empty() {
|
|
||||||
for st in sts.iter() {
|
|
||||||
chn.send(bytes!(format!("{st};"))).await?;
|
|
||||||
}
|
|
||||||
chn.send(bytes!("")).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chn.send(bytes!("")).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Output TABLES
|
// Output TABLES
|
||||||
{
|
{
|
||||||
let tbs = self.all_tb(ns, db).await?;
|
let tbs = self.all_tb(ns, db).await?;
|
||||||
|
|
78
core/src/sql/access.rs
Normal file
78
core/src/sql/access.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use crate::sql::{escape::escape_ident, fmt::Fmt, strand::no_nul_bytes, Id, Ident, Thing};
|
||||||
|
use revision::revisioned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Accesses(pub Vec<Access>);
|
||||||
|
|
||||||
|
impl From<Access> for Accesses {
|
||||||
|
fn from(v: Access) -> Self {
|
||||||
|
Accesses(vec![v])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Accesses {
|
||||||
|
type Target = Vec<Access>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Accesses {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(&Fmt::comma_separated(&self.0), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
||||||
|
#[serde(rename = "$surrealdb::private::sql::Access")]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Access(#[serde(with = "no_nul_bytes")] pub String);
|
||||||
|
|
||||||
|
impl From<String> for Access {
|
||||||
|
fn from(v: String) -> Self {
|
||||||
|
Self(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Access {
|
||||||
|
fn from(v: &str) -> Self {
|
||||||
|
Self::from(String::from(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Ident> for Access {
|
||||||
|
fn from(v: Ident) -> Self {
|
||||||
|
Self(v.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Access {
|
||||||
|
type Target = String;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Access {
|
||||||
|
pub fn generate(&self) -> Thing {
|
||||||
|
Thing {
|
||||||
|
tb: self.0.to_owned(),
|
||||||
|
id: Id::rand(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Access {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(&escape_ident(&self.0), f)
|
||||||
|
}
|
||||||
|
}
|
251
core/src/sql/access_type.rs
Normal file
251
core/src/sql/access_type.rs
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
use crate::sql::statements::info::InfoStructure;
|
||||||
|
use crate::sql::statements::DefineAccessStatement;
|
||||||
|
use crate::sql::{escape::quote_str, Algorithm, Duration};
|
||||||
|
use revision::revisioned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use super::{Object, Value};
|
||||||
|
|
||||||
|
/// The type of access methods available
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum AccessType {
|
||||||
|
Record(RecordAccess),
|
||||||
|
Jwt(JwtAccess),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AccessType {
|
||||||
|
fn default() -> Self {
|
||||||
|
// Access type defaults to the most specific
|
||||||
|
Self::Record(RecordAccess {
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
pub struct JwtAccess {
|
||||||
|
// Verify is required
|
||||||
|
pub verify: JwtAccessVerify,
|
||||||
|
// Issue is optional
|
||||||
|
// It is possible to only verify externally issued tokens
|
||||||
|
pub issue: Option<JwtAccessIssue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for JwtAccess {
|
||||||
|
fn default() -> Self {
|
||||||
|
// Defaults to HS512 with a randomly generated key
|
||||||
|
let alg = Algorithm::Hs512;
|
||||||
|
let key = DefineAccessStatement::random_key();
|
||||||
|
// By default the access method can verify and issue tokens
|
||||||
|
Self {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg,
|
||||||
|
key: key.clone(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg,
|
||||||
|
key,
|
||||||
|
// Defaults to tokens lasting for one hour
|
||||||
|
duration: Some(Duration::from_hours(1)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
pub struct JwtAccessIssue {
|
||||||
|
pub alg: Algorithm,
|
||||||
|
pub key: String,
|
||||||
|
pub duration: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for JwtAccessIssue {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
// Defaults to HS512
|
||||||
|
alg: Algorithm::Hs512,
|
||||||
|
// Avoid defaulting to empty key
|
||||||
|
key: DefineAccessStatement::random_key(),
|
||||||
|
// Defaults to tokens lasting for one hour
|
||||||
|
duration: Some(Duration::from_hours(1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum JwtAccessVerify {
|
||||||
|
Key(JwtAccessVerifyKey),
|
||||||
|
Jwks(JwtAccessVerifyJwks),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for JwtAccessVerify {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Key(JwtAccessVerifyKey {
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
pub struct JwtAccessVerifyKey {
|
||||||
|
pub alg: Algorithm,
|
||||||
|
pub key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for JwtAccessVerifyKey {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
// Defaults to HS512
|
||||||
|
alg: Algorithm::Hs512,
|
||||||
|
// Avoid defaulting to empty key
|
||||||
|
key: DefineAccessStatement::random_key(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
pub struct JwtAccessVerifyJwks {
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
pub struct RecordAccess {
|
||||||
|
pub duration: Option<Duration>,
|
||||||
|
pub signup: Option<Value>,
|
||||||
|
pub signin: Option<Value>,
|
||||||
|
pub jwt: JwtAccess,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RecordAccess {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
// Defaults to sessions lasting one hour
|
||||||
|
duration: Some(Duration::from_hours(1)),
|
||||||
|
signup: None,
|
||||||
|
signin: None,
|
||||||
|
jwt: JwtAccess {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for AccessType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
AccessType::Jwt(ac) => {
|
||||||
|
f.write_str(" JWT")?;
|
||||||
|
match &ac.verify {
|
||||||
|
JwtAccessVerify::Key(ref v) => {
|
||||||
|
write!(f, " ALGORITHM {} KEY {}", v.alg, quote_str(&v.key))?
|
||||||
|
}
|
||||||
|
JwtAccessVerify::Jwks(ref v) => write!(f, " JWKS {}", quote_str(&v.url))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccessType::Record(ac) => {
|
||||||
|
f.write_str(" RECORD")?;
|
||||||
|
if let Some(ref v) = ac.duration {
|
||||||
|
write!(f, " DURATION {v}")?
|
||||||
|
}
|
||||||
|
if let Some(ref v) = ac.signup {
|
||||||
|
write!(f, " SIGNUP {v}")?
|
||||||
|
}
|
||||||
|
if let Some(ref v) = ac.signin {
|
||||||
|
write!(f, " SIGNIN {v}")?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InfoStructure for AccessType {
|
||||||
|
fn structure(self) -> Value {
|
||||||
|
let mut acc = Object::default();
|
||||||
|
|
||||||
|
match self {
|
||||||
|
AccessType::Jwt(ac) => {
|
||||||
|
acc.insert("kind".to_string(), "JWT".into());
|
||||||
|
acc.insert("jwt".to_string(), ac.structure());
|
||||||
|
}
|
||||||
|
AccessType::Record(ac) => {
|
||||||
|
acc.insert("kind".to_string(), "RECORD".into());
|
||||||
|
if let Some(signup) = ac.signup {
|
||||||
|
acc.insert("signup".to_string(), signup.structure());
|
||||||
|
}
|
||||||
|
if let Some(signin) = ac.signin {
|
||||||
|
acc.insert("signin".to_string(), signin.structure());
|
||||||
|
}
|
||||||
|
if let Some(duration) = ac.duration {
|
||||||
|
acc.insert("duration".to_string(), duration.into());
|
||||||
|
}
|
||||||
|
acc.insert("jwt".to_string(), ac.jwt.structure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::Object(acc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for JwtAccess {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match &self.verify {
|
||||||
|
JwtAccessVerify::Key(ref v) => {
|
||||||
|
write!(f, "ALGORITHM {} KEY {}", v.alg, quote_str(&v.key))?;
|
||||||
|
}
|
||||||
|
JwtAccessVerify::Jwks(ref v) => {
|
||||||
|
write!(f, "JWKS {}", quote_str(&v.url),)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(iss) = &self.issue {
|
||||||
|
write!(f, " WITH ISSUER KEY {}", quote_str(&iss.key))?;
|
||||||
|
if let Some(ref v) = iss.duration {
|
||||||
|
write!(f, " DURATION {v}")?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InfoStructure for JwtAccess {
|
||||||
|
fn structure(self) -> Value {
|
||||||
|
let mut acc = Object::default();
|
||||||
|
match self.verify {
|
||||||
|
JwtAccessVerify::Key(v) => {
|
||||||
|
acc.insert("alg".to_string(), v.alg.structure());
|
||||||
|
acc.insert("key".to_string(), v.key.into());
|
||||||
|
}
|
||||||
|
JwtAccessVerify::Jwks(v) => {
|
||||||
|
acc.insert("jwks".to_string(), v.url.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(v) = self.issue {
|
||||||
|
let mut iss = Object::default();
|
||||||
|
iss.insert("alg".to_string(), v.alg.structure());
|
||||||
|
iss.insert("key".to_string(), v.key.into());
|
||||||
|
if let Some(t) = v.duration {
|
||||||
|
iss.insert("duration".to_string(), t.into());
|
||||||
|
}
|
||||||
|
acc.insert("issuer".to_string(), iss.to_string().into());
|
||||||
|
}
|
||||||
|
Value::Object(acc)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,33 @@ pub enum Algorithm {
|
||||||
Rs256,
|
Rs256,
|
||||||
Rs384,
|
Rs384,
|
||||||
Rs512,
|
Rs512,
|
||||||
Jwks, // Not an argorithm.
|
}
|
||||||
|
|
||||||
|
impl Algorithm {
|
||||||
|
// Does the algorithm us the same key for signing and verification?
|
||||||
|
pub(crate) fn is_symmetric(self) -> bool {
|
||||||
|
matches!(self, Algorithm::Hs256 | Algorithm::Hs384 | Algorithm::Hs512)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Algorithm> for jsonwebtoken::Algorithm {
|
||||||
|
fn from(val: Algorithm) -> Self {
|
||||||
|
match val {
|
||||||
|
Algorithm::Hs256 => jsonwebtoken::Algorithm::HS256,
|
||||||
|
Algorithm::Hs384 => jsonwebtoken::Algorithm::HS384,
|
||||||
|
Algorithm::Hs512 => jsonwebtoken::Algorithm::HS512,
|
||||||
|
Algorithm::EdDSA => jsonwebtoken::Algorithm::EdDSA,
|
||||||
|
Algorithm::Es256 => jsonwebtoken::Algorithm::ES256,
|
||||||
|
Algorithm::Es384 => jsonwebtoken::Algorithm::ES384,
|
||||||
|
Algorithm::Es512 => jsonwebtoken::Algorithm::ES384,
|
||||||
|
Algorithm::Ps256 => jsonwebtoken::Algorithm::PS256,
|
||||||
|
Algorithm::Ps384 => jsonwebtoken::Algorithm::PS384,
|
||||||
|
Algorithm::Ps512 => jsonwebtoken::Algorithm::PS512,
|
||||||
|
Algorithm::Rs256 => jsonwebtoken::Algorithm::RS256,
|
||||||
|
Algorithm::Rs384 => jsonwebtoken::Algorithm::RS384,
|
||||||
|
Algorithm::Rs512 => jsonwebtoken::Algorithm::RS512,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Algorithm {
|
impl Default for Algorithm {
|
||||||
|
@ -47,7 +73,6 @@ impl fmt::Display for Algorithm {
|
||||||
Self::Rs256 => "RS256",
|
Self::Rs256 => "RS256",
|
||||||
Self::Rs384 => "RS384",
|
Self::Rs384 => "RS384",
|
||||||
Self::Rs512 => "RS512",
|
Self::Rs512 => "RS512",
|
||||||
Self::Jwks => "JWKS", // Not an algorithm.
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub enum Base {
|
||||||
Root,
|
Root,
|
||||||
Ns,
|
Ns,
|
||||||
Db,
|
Db,
|
||||||
|
// TODO(gguillemas): This variant is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||||
Sc(Ident),
|
Sc(Ident),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ impl fmt::Display for Base {
|
||||||
match self {
|
match self {
|
||||||
Self::Ns => f.write_str("NAMESPACE"),
|
Self::Ns => f.write_str("NAMESPACE"),
|
||||||
Self::Db => f.write_str("DATABASE"),
|
Self::Db => f.write_str("DATABASE"),
|
||||||
|
// TODO(gguillemas): This variant is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||||
Self::Sc(sc) => write!(f, "SCOPE {sc}"),
|
Self::Sc(sc) => write!(f, "SCOPE {sc}"),
|
||||||
Self::Root => f.write_str("ROOT"),
|
Self::Root => f.write_str("ROOT"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
//! The full type definitions for the SurrealQL query language
|
//! The full type definitions for the SurrealQL query language
|
||||||
|
|
||||||
|
pub(crate) mod access;
|
||||||
|
pub(crate) mod access_type;
|
||||||
pub(crate) mod algorithm;
|
pub(crate) mod algorithm;
|
||||||
#[cfg(feature = "arbitrary")]
|
#[cfg(feature = "arbitrary")]
|
||||||
pub(crate) mod arbitrary;
|
pub(crate) mod arbitrary;
|
||||||
|
@ -74,6 +76,9 @@ pub mod index;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
pub mod statements;
|
pub mod statements;
|
||||||
|
|
||||||
|
pub use self::access::Access;
|
||||||
|
pub use self::access::Accesses;
|
||||||
|
pub use self::access_type::{AccessType, JwtAccess, RecordAccess};
|
||||||
pub use self::algorithm::Algorithm;
|
pub use self::algorithm::Algorithm;
|
||||||
pub use self::array::Array;
|
pub use self::array::Array;
|
||||||
pub use self::base::Base;
|
pub use self::base::Base;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::sql::part::Part;
|
use crate::sql::part::Part;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
pub const OBJ_PATH_AUTH: &str = "sd";
|
pub const OBJ_PATH_ACCESS: &str = "ac";
|
||||||
pub const OBJ_PATH_SCOPE: &str = "sc";
|
pub const OBJ_PATH_AUTH: &str = "rd";
|
||||||
pub const OBJ_PATH_TOKEN: &str = "tk";
|
pub const OBJ_PATH_TOKEN: &str = "tk";
|
||||||
|
|
||||||
pub static ID: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("id")]);
|
pub static ID: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("id")]);
|
||||||
|
|
||||||
pub static IP: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("ip")]);
|
pub static IP: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("ip")]);
|
||||||
|
@ -12,9 +13,9 @@ pub static NS: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("ns")]);
|
||||||
|
|
||||||
pub static DB: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("db")]);
|
pub static DB: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("db")]);
|
||||||
|
|
||||||
pub static SC: Lazy<[Part; 1]> = Lazy::new(|| [Part::from(OBJ_PATH_SCOPE)]);
|
pub static AC: Lazy<[Part; 1]> = Lazy::new(|| [Part::from(OBJ_PATH_ACCESS)]);
|
||||||
|
|
||||||
pub static SD: Lazy<[Part; 1]> = Lazy::new(|| [Part::from(OBJ_PATH_AUTH)]);
|
pub static RD: Lazy<[Part; 1]> = Lazy::new(|| [Part::from(OBJ_PATH_AUTH)]);
|
||||||
|
|
||||||
pub static OR: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("or")]);
|
pub static OR: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("or")]);
|
||||||
|
|
||||||
|
|
|
@ -4,27 +4,37 @@ use crate::doc::CursorDoc;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::iam::{Action, ResourceKind};
|
use crate::iam::{Action, ResourceKind};
|
||||||
use crate::sql::statements::info::InfoStructure;
|
use crate::sql::statements::info::InfoStructure;
|
||||||
use crate::sql::{escape::quote_str, Algorithm, Base, Ident, Object, Strand, Value};
|
use crate::sql::{AccessType, Base, Ident, Object, Strand, Value};
|
||||||
use derive::Store;
|
use derive::Store;
|
||||||
|
use rand::distributions::Alphanumeric;
|
||||||
|
use rand::Rng;
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
#[revisioned(revision = 2)]
|
#[revisioned(revision = 2)]
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
#[derive(Clone, Default, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct DefineTokenStatement {
|
pub struct DefineAccessStatement {
|
||||||
pub name: Ident,
|
pub name: Ident,
|
||||||
pub base: Base,
|
pub base: Base,
|
||||||
pub kind: Algorithm,
|
pub kind: AccessType,
|
||||||
pub code: String,
|
|
||||||
pub comment: Option<Strand>,
|
pub comment: Option<Strand>,
|
||||||
#[revision(start = 2)]
|
#[revision(start = 2)]
|
||||||
pub if_not_exists: bool,
|
pub if_not_exists: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefineTokenStatement {
|
impl DefineAccessStatement {
|
||||||
|
/// Generate a random key to be used to sign session tokens
|
||||||
|
/// This key will be used to sign tokens issued with this access method
|
||||||
|
/// This value is used by default in every access method other than JWT
|
||||||
|
pub(crate) fn random_key() -> String {
|
||||||
|
rand::thread_rng().sample_iter(&Alphanumeric).take(128).map(char::from).collect::<String>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefineAccessStatement {
|
||||||
/// Process this type returning a computed simple Value
|
/// Process this type returning a computed simple Value
|
||||||
pub(crate) async fn compute(
|
pub(crate) async fn compute(
|
||||||
&self,
|
&self,
|
||||||
|
@ -41,18 +51,18 @@ impl DefineTokenStatement {
|
||||||
let mut run = txn.lock().await;
|
let mut run = txn.lock().await;
|
||||||
// Clear the cache
|
// Clear the cache
|
||||||
run.clear_cache();
|
run.clear_cache();
|
||||||
// Check if token already exists
|
// Check if access method already exists
|
||||||
if self.if_not_exists && run.get_ns_token(opt.ns(), &self.name).await.is_ok() {
|
if self.if_not_exists && run.get_ns_access(opt.ns(), &self.name).await.is_ok() {
|
||||||
return Err(Error::NtAlreadyExists {
|
return Err(Error::AccessNsAlreadyExists {
|
||||||
value: self.name.to_string(),
|
value: self.name.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Process the statement
|
// Process the statement
|
||||||
let key = crate::key::namespace::tk::new(opt.ns(), &self.name);
|
let key = crate::key::namespace::ac::new(opt.ns(), &self.name);
|
||||||
run.add_ns(opt.ns(), opt.strict).await?;
|
run.add_ns(opt.ns(), opt.strict).await?;
|
||||||
run.set(
|
run.set(
|
||||||
key,
|
key,
|
||||||
DefineTokenStatement {
|
DefineAccessStatement {
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
..self.clone()
|
..self.clone()
|
||||||
},
|
},
|
||||||
|
@ -66,50 +76,21 @@ impl DefineTokenStatement {
|
||||||
let mut run = txn.lock().await;
|
let mut run = txn.lock().await;
|
||||||
// Clear the cache
|
// Clear the cache
|
||||||
run.clear_cache();
|
run.clear_cache();
|
||||||
// Check if token already exists
|
// Check if access method already exists
|
||||||
if self.if_not_exists
|
if self.if_not_exists
|
||||||
&& run.get_db_token(opt.ns(), opt.db(), &self.name).await.is_ok()
|
&& run.get_db_access(opt.ns(), opt.db(), &self.name).await.is_ok()
|
||||||
{
|
{
|
||||||
return Err(Error::DtAlreadyExists {
|
return Err(Error::AccessDbAlreadyExists {
|
||||||
value: self.name.to_string(),
|
value: self.name.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Process the statement
|
// Process the statement
|
||||||
let key = crate::key::database::tk::new(opt.ns(), opt.db(), &self.name);
|
let key = crate::key::database::ac::new(opt.ns(), opt.db(), &self.name);
|
||||||
run.add_ns(opt.ns(), opt.strict).await?;
|
run.add_ns(opt.ns(), opt.strict).await?;
|
||||||
run.add_db(opt.ns(), opt.db(), opt.strict).await?;
|
run.add_db(opt.ns(), opt.db(), opt.strict).await?;
|
||||||
run.set(
|
run.set(
|
||||||
key,
|
key,
|
||||||
DefineTokenStatement {
|
DefineAccessStatement {
|
||||||
if_not_exists: false,
|
|
||||||
..self.clone()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
// Ok all good
|
|
||||||
Ok(Value::None)
|
|
||||||
}
|
|
||||||
Base::Sc(sc) => {
|
|
||||||
// Claim transaction
|
|
||||||
let mut run = txn.lock().await;
|
|
||||||
// Clear the cache
|
|
||||||
run.clear_cache();
|
|
||||||
// Check if token already exists
|
|
||||||
if self.if_not_exists
|
|
||||||
&& run.get_sc_token(opt.ns(), opt.db(), sc, &self.name).await.is_ok()
|
|
||||||
{
|
|
||||||
return Err(Error::StAlreadyExists {
|
|
||||||
value: self.name.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Process the statement
|
|
||||||
let key = crate::key::scope::tk::new(opt.ns(), opt.db(), sc, &self.name);
|
|
||||||
run.add_ns(opt.ns(), opt.strict).await?;
|
|
||||||
run.add_db(opt.ns(), opt.db(), opt.strict).await?;
|
|
||||||
run.add_sc(opt.ns(), opt.db(), sc, opt.strict).await?;
|
|
||||||
run.set(
|
|
||||||
key,
|
|
||||||
DefineTokenStatement {
|
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
..self.clone()
|
..self.clone()
|
||||||
},
|
},
|
||||||
|
@ -124,20 +105,31 @@ impl DefineTokenStatement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for DefineTokenStatement {
|
impl Display for DefineAccessStatement {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "DEFINE TOKEN",)?;
|
write!(f, "DEFINE ACCESS",)?;
|
||||||
if self.if_not_exists {
|
if self.if_not_exists {
|
||||||
write!(f, " IF NOT EXISTS")?
|
write!(f, " IF NOT EXISTS")?
|
||||||
}
|
}
|
||||||
write!(
|
write!(f, " {} ON {}", self.name, self.base)?;
|
||||||
f,
|
match &self.kind {
|
||||||
" {} ON {} TYPE {} VALUE {}",
|
AccessType::Jwt(ac) => {
|
||||||
self.name,
|
write!(f, " TYPE JWT {}", ac)?;
|
||||||
self.base,
|
}
|
||||||
self.kind,
|
AccessType::Record(ac) => {
|
||||||
quote_str(&self.code)
|
write!(f, " TYPE RECORD")?;
|
||||||
)?;
|
if let Some(ref v) = ac.duration {
|
||||||
|
write!(f, " DURATION {v}")?
|
||||||
|
}
|
||||||
|
if let Some(ref v) = ac.signup {
|
||||||
|
write!(f, " SIGNUP {v}")?
|
||||||
|
}
|
||||||
|
if let Some(ref v) = ac.signin {
|
||||||
|
write!(f, " SIGNIN {v}")?
|
||||||
|
}
|
||||||
|
write!(f, " WITH JWT {}", ac.jwt)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(ref v) = self.comment {
|
if let Some(ref v) = self.comment {
|
||||||
write!(f, " COMMENT {v}")?
|
write!(f, " COMMENT {v}")?
|
||||||
}
|
}
|
||||||
|
@ -145,13 +137,12 @@ impl Display for DefineTokenStatement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InfoStructure for DefineTokenStatement {
|
impl InfoStructure for DefineAccessStatement {
|
||||||
fn structure(self) -> Value {
|
fn structure(self) -> Value {
|
||||||
let Self {
|
let Self {
|
||||||
name,
|
name,
|
||||||
base,
|
base,
|
||||||
kind,
|
kind,
|
||||||
code,
|
|
||||||
comment,
|
comment,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
@ -163,8 +154,6 @@ impl InfoStructure for DefineTokenStatement {
|
||||||
|
|
||||||
acc.insert("kind".to_string(), kind.structure());
|
acc.insert("kind".to_string(), kind.structure());
|
||||||
|
|
||||||
acc.insert("code".to_string(), code.into());
|
|
||||||
|
|
||||||
if let Some(comment) = comment {
|
if let Some(comment) = comment {
|
||||||
acc.insert("comment".to_string(), comment.into());
|
acc.insert("comment".to_string(), comment.into());
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod access;
|
||||||
mod analyzer;
|
mod analyzer;
|
||||||
mod database;
|
mod database;
|
||||||
mod event;
|
mod event;
|
||||||
|
@ -7,11 +8,10 @@ mod index;
|
||||||
mod model;
|
mod model;
|
||||||
mod namespace;
|
mod namespace;
|
||||||
mod param;
|
mod param;
|
||||||
mod scope;
|
|
||||||
mod table;
|
mod table;
|
||||||
mod token;
|
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
|
pub use access::DefineAccessStatement;
|
||||||
pub use analyzer::DefineAnalyzerStatement;
|
pub use analyzer::DefineAnalyzerStatement;
|
||||||
pub use database::DefineDatabaseStatement;
|
pub use database::DefineDatabaseStatement;
|
||||||
pub use event::DefineEventStatement;
|
pub use event::DefineEventStatement;
|
||||||
|
@ -21,9 +21,7 @@ pub use index::DefineIndexStatement;
|
||||||
pub use model::DefineModelStatement;
|
pub use model::DefineModelStatement;
|
||||||
pub use namespace::DefineNamespaceStatement;
|
pub use namespace::DefineNamespaceStatement;
|
||||||
pub use param::DefineParamStatement;
|
pub use param::DefineParamStatement;
|
||||||
pub use scope::DefineScopeStatement;
|
|
||||||
pub use table::DefineTableStatement;
|
pub use table::DefineTableStatement;
|
||||||
pub use token::DefineTokenStatement;
|
|
||||||
pub use user::DefineUserStatement;
|
pub use user::DefineUserStatement;
|
||||||
|
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
|
@ -47,8 +45,6 @@ pub enum DefineStatement {
|
||||||
Database(DefineDatabaseStatement),
|
Database(DefineDatabaseStatement),
|
||||||
Function(DefineFunctionStatement),
|
Function(DefineFunctionStatement),
|
||||||
Analyzer(DefineAnalyzerStatement),
|
Analyzer(DefineAnalyzerStatement),
|
||||||
Token(DefineTokenStatement),
|
|
||||||
Scope(DefineScopeStatement),
|
|
||||||
Param(DefineParamStatement),
|
Param(DefineParamStatement),
|
||||||
Table(DefineTableStatement),
|
Table(DefineTableStatement),
|
||||||
Event(DefineEventStatement),
|
Event(DefineEventStatement),
|
||||||
|
@ -56,6 +52,7 @@ pub enum DefineStatement {
|
||||||
Index(DefineIndexStatement),
|
Index(DefineIndexStatement),
|
||||||
User(DefineUserStatement),
|
User(DefineUserStatement),
|
||||||
Model(DefineModelStatement),
|
Model(DefineModelStatement),
|
||||||
|
Access(DefineAccessStatement),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefineStatement {
|
impl DefineStatement {
|
||||||
|
@ -76,8 +73,6 @@ impl DefineStatement {
|
||||||
Self::Namespace(ref v) => v.compute(ctx, opt, txn, doc).await,
|
Self::Namespace(ref v) => v.compute(ctx, opt, txn, doc).await,
|
||||||
Self::Database(ref v) => v.compute(ctx, opt, txn, doc).await,
|
Self::Database(ref v) => v.compute(ctx, opt, txn, doc).await,
|
||||||
Self::Function(ref v) => v.compute(ctx, opt, txn, doc).await,
|
Self::Function(ref v) => v.compute(ctx, opt, txn, doc).await,
|
||||||
Self::Token(ref v) => v.compute(ctx, opt, txn, doc).await,
|
|
||||||
Self::Scope(ref v) => v.compute(ctx, opt, txn, doc).await,
|
|
||||||
Self::Param(ref v) => v.compute(stk, ctx, opt, txn, doc).await,
|
Self::Param(ref v) => v.compute(stk, ctx, opt, txn, doc).await,
|
||||||
Self::Table(ref v) => v.compute(stk, ctx, opt, txn, doc).await,
|
Self::Table(ref v) => v.compute(stk, ctx, opt, txn, doc).await,
|
||||||
Self::Event(ref v) => v.compute(ctx, opt, txn, doc).await,
|
Self::Event(ref v) => v.compute(ctx, opt, txn, doc).await,
|
||||||
|
@ -86,6 +81,7 @@ impl DefineStatement {
|
||||||
Self::Analyzer(ref v) => v.compute(ctx, opt, txn, doc).await,
|
Self::Analyzer(ref v) => v.compute(ctx, opt, txn, doc).await,
|
||||||
Self::User(ref v) => v.compute(ctx, opt, txn, doc).await,
|
Self::User(ref v) => v.compute(ctx, opt, txn, doc).await,
|
||||||
Self::Model(ref v) => v.compute(ctx, opt, txn, doc).await,
|
Self::Model(ref v) => v.compute(ctx, opt, txn, doc).await,
|
||||||
|
Self::Access(ref v) => v.compute(ctx, opt, txn, doc).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,8 +93,6 @@ impl Display for DefineStatement {
|
||||||
Self::Database(v) => Display::fmt(v, f),
|
Self::Database(v) => Display::fmt(v, f),
|
||||||
Self::Function(v) => Display::fmt(v, f),
|
Self::Function(v) => Display::fmt(v, f),
|
||||||
Self::User(v) => Display::fmt(v, f),
|
Self::User(v) => Display::fmt(v, f),
|
||||||
Self::Token(v) => Display::fmt(v, f),
|
|
||||||
Self::Scope(v) => Display::fmt(v, f),
|
|
||||||
Self::Param(v) => Display::fmt(v, f),
|
Self::Param(v) => Display::fmt(v, f),
|
||||||
Self::Table(v) => Display::fmt(v, f),
|
Self::Table(v) => Display::fmt(v, f),
|
||||||
Self::Event(v) => Display::fmt(v, f),
|
Self::Event(v) => Display::fmt(v, f),
|
||||||
|
@ -106,6 +100,7 @@ impl Display for DefineStatement {
|
||||||
Self::Index(v) => Display::fmt(v, f),
|
Self::Index(v) => Display::fmt(v, f),
|
||||||
Self::Analyzer(v) => Display::fmt(v, f),
|
Self::Analyzer(v) => Display::fmt(v, f),
|
||||||
Self::Model(v) => Display::fmt(v, f),
|
Self::Model(v) => Display::fmt(v, f),
|
||||||
|
Self::Access(v) => Display::fmt(v, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::{Options, Transaction};
|
|
||||||
use crate::doc::CursorDoc;
|
|
||||||
use crate::err::Error;
|
|
||||||
use crate::iam::{Action, ResourceKind};
|
|
||||||
use crate::sql::statements::info::InfoStructure;
|
|
||||||
use crate::sql::{Base, Duration, Ident, Object, Strand, Value};
|
|
||||||
use derive::Store;
|
|
||||||
use rand::distributions::Alphanumeric;
|
|
||||||
use rand::Rng;
|
|
||||||
use revision::revisioned;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::fmt::{self, Display};
|
|
||||||
|
|
||||||
#[revisioned(revision = 2)]
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct DefineScopeStatement {
|
|
||||||
pub name: Ident,
|
|
||||||
pub code: String,
|
|
||||||
pub session: Option<Duration>,
|
|
||||||
pub signup: Option<Value>,
|
|
||||||
pub signin: Option<Value>,
|
|
||||||
pub comment: Option<Strand>,
|
|
||||||
#[revision(start = 2)]
|
|
||||||
pub if_not_exists: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DefineScopeStatement {
|
|
||||||
pub(crate) fn random_code() -> String {
|
|
||||||
rand::thread_rng().sample_iter(&Alphanumeric).take(128).map(char::from).collect::<String>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DefineScopeStatement {
|
|
||||||
/// Process this type returning a computed simple Value
|
|
||||||
pub(crate) async fn compute(
|
|
||||||
&self,
|
|
||||||
_ctx: &Context<'_>,
|
|
||||||
opt: &Options,
|
|
||||||
txn: &Transaction,
|
|
||||||
_doc: Option<&CursorDoc<'_>>,
|
|
||||||
) -> Result<Value, Error> {
|
|
||||||
// Allowed to run?
|
|
||||||
opt.is_allowed(Action::Edit, ResourceKind::Scope, &Base::Db)?;
|
|
||||||
// Claim transaction
|
|
||||||
let mut run = txn.lock().await;
|
|
||||||
// Clear the cache
|
|
||||||
run.clear_cache();
|
|
||||||
// Check if scope already exists
|
|
||||||
if self.if_not_exists && run.get_sc(opt.ns(), opt.db(), &self.name).await.is_ok() {
|
|
||||||
return Err(Error::ScAlreadyExists {
|
|
||||||
value: self.name.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Process the statement
|
|
||||||
let key = crate::key::database::sc::new(opt.ns(), opt.db(), &self.name);
|
|
||||||
run.add_ns(opt.ns(), opt.strict).await?;
|
|
||||||
run.add_db(opt.ns(), opt.db(), opt.strict).await?;
|
|
||||||
run.set(
|
|
||||||
key,
|
|
||||||
DefineScopeStatement {
|
|
||||||
// Don't persist the "IF NOT EXISTS" clause to schema
|
|
||||||
if_not_exists: false,
|
|
||||||
..self.clone()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
// Ok all good
|
|
||||||
Ok(Value::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for DefineScopeStatement {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "DEFINE SCOPE")?;
|
|
||||||
if self.if_not_exists {
|
|
||||||
write!(f, " IF NOT EXISTS")?
|
|
||||||
}
|
|
||||||
write!(f, " {}", self.name)?;
|
|
||||||
if let Some(ref v) = self.session {
|
|
||||||
write!(f, " SESSION {v}")?
|
|
||||||
}
|
|
||||||
if let Some(ref v) = self.signup {
|
|
||||||
write!(f, " SIGNUP {v}")?
|
|
||||||
}
|
|
||||||
if let Some(ref v) = self.signin {
|
|
||||||
write!(f, " SIGNIN {v}")?
|
|
||||||
}
|
|
||||||
if let Some(ref v) = self.comment {
|
|
||||||
write!(f, " COMMENT {v}")?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InfoStructure for DefineScopeStatement {
|
|
||||||
fn structure(self) -> Value {
|
|
||||||
let Self {
|
|
||||||
name,
|
|
||||||
signup,
|
|
||||||
signin,
|
|
||||||
comment,
|
|
||||||
session,
|
|
||||||
..
|
|
||||||
} = self;
|
|
||||||
let mut acc = Object::default();
|
|
||||||
|
|
||||||
acc.insert("name".to_string(), name.structure());
|
|
||||||
|
|
||||||
if let Some(signup) = signup {
|
|
||||||
acc.insert("signup".to_string(), signup.structure());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(signin) = signin {
|
|
||||||
acc.insert("signin".to_string(), signin.structure());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(comment) = comment {
|
|
||||||
acc.insert("comment".to_string(), comment.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(duration) = session {
|
|
||||||
acc.insert("duration".to_string(), duration.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Object(acc)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,10 +27,6 @@ pub enum InfoStatement {
|
||||||
Db,
|
Db,
|
||||||
#[revision(start = 2)]
|
#[revision(start = 2)]
|
||||||
Db(bool),
|
Db(bool),
|
||||||
#[revision(end = 2, convert_fn = "sc_migrate")]
|
|
||||||
Sc(Ident),
|
|
||||||
#[revision(start = 2)]
|
|
||||||
Sc(Ident, bool),
|
|
||||||
#[revision(end = 2, convert_fn = "tb_migrate")]
|
#[revision(end = 2, convert_fn = "tb_migrate")]
|
||||||
Tb(Ident),
|
Tb(Ident),
|
||||||
#[revision(start = 2)]
|
#[revision(start = 2)]
|
||||||
|
@ -54,10 +50,6 @@ impl InfoStatement {
|
||||||
Ok(Self::Db(false))
|
Ok(Self::Db(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sc_migrate(_revision: u16, i: (Ident,)) -> Result<Self, revision::Error> {
|
|
||||||
Ok(Self::Sc(i.0, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tb_migrate(_revision: u16, n: (Ident,)) -> Result<Self, revision::Error> {
|
fn tb_migrate(_revision: u16, n: (Ident,)) -> Result<Self, revision::Error> {
|
||||||
Ok(Self::Tb(n.0, false))
|
Ok(Self::Tb(n.0, false))
|
||||||
}
|
}
|
||||||
|
@ -122,12 +114,12 @@ impl InfoStatement {
|
||||||
tmp.insert(v.name.to_string(), v.to_string().into());
|
tmp.insert(v.name.to_string(), v.to_string().into());
|
||||||
}
|
}
|
||||||
res.insert("users".to_owned(), tmp.into());
|
res.insert("users".to_owned(), tmp.into());
|
||||||
// Process the tokens
|
// Process the accesses
|
||||||
let mut tmp = Object::default();
|
let mut tmp = Object::default();
|
||||||
for v in run.all_ns_tokens(opt.ns()).await?.iter() {
|
for v in run.all_ns_accesses(opt.ns()).await?.iter() {
|
||||||
tmp.insert(v.name.to_string(), v.to_string().into());
|
tmp.insert(v.name.to_string(), v.to_string().into());
|
||||||
}
|
}
|
||||||
res.insert("tokens".to_owned(), tmp.into());
|
res.insert("accesses".to_owned(), tmp.into());
|
||||||
// Ok all good
|
// Ok all good
|
||||||
Value::from(res).ok()
|
Value::from(res).ok()
|
||||||
}
|
}
|
||||||
|
@ -144,12 +136,6 @@ impl InfoStatement {
|
||||||
tmp.insert(v.name.to_string(), v.to_string().into());
|
tmp.insert(v.name.to_string(), v.to_string().into());
|
||||||
}
|
}
|
||||||
res.insert("users".to_owned(), tmp.into());
|
res.insert("users".to_owned(), tmp.into());
|
||||||
// Process the tokens
|
|
||||||
let mut tmp = Object::default();
|
|
||||||
for v in run.all_db_tokens(opt.ns(), opt.db()).await?.iter() {
|
|
||||||
tmp.insert(v.name.to_string(), v.to_string().into());
|
|
||||||
}
|
|
||||||
res.insert("tokens".to_owned(), tmp.into());
|
|
||||||
// Process the functions
|
// Process the functions
|
||||||
let mut tmp = Object::default();
|
let mut tmp = Object::default();
|
||||||
for v in run.all_db_functions(opt.ns(), opt.db()).await?.iter() {
|
for v in run.all_db_functions(opt.ns(), opt.db()).await?.iter() {
|
||||||
|
@ -168,12 +154,12 @@ impl InfoStatement {
|
||||||
tmp.insert(v.name.to_string(), v.to_string().into());
|
tmp.insert(v.name.to_string(), v.to_string().into());
|
||||||
}
|
}
|
||||||
res.insert("params".to_owned(), tmp.into());
|
res.insert("params".to_owned(), tmp.into());
|
||||||
// Process the scopes
|
// Process the accesses
|
||||||
let mut tmp = Object::default();
|
let mut tmp = Object::default();
|
||||||
for v in run.all_sc(opt.ns(), opt.db()).await?.iter() {
|
for v in run.all_db_accesses(opt.ns(), opt.db()).await?.iter() {
|
||||||
tmp.insert(v.name.to_string(), v.to_string().into());
|
tmp.insert(v.name.to_string(), v.to_string().into());
|
||||||
}
|
}
|
||||||
res.insert("scopes".to_owned(), tmp.into());
|
res.insert("accesses".to_owned(), tmp.into());
|
||||||
// Process the tables
|
// Process the tables
|
||||||
let mut tmp = Object::default();
|
let mut tmp = Object::default();
|
||||||
for v in run.all_tb(opt.ns(), opt.db()).await?.iter() {
|
for v in run.all_tb(opt.ns(), opt.db()).await?.iter() {
|
||||||
|
@ -189,22 +175,6 @@ impl InfoStatement {
|
||||||
// Ok all good
|
// Ok all good
|
||||||
Value::from(res).ok()
|
Value::from(res).ok()
|
||||||
}
|
}
|
||||||
InfoStatement::Sc(sc, false) => {
|
|
||||||
// Allowed to run?
|
|
||||||
opt.is_allowed(Action::View, ResourceKind::Any, &Base::Db)?;
|
|
||||||
// Claim transaction
|
|
||||||
let mut run = txn.lock().await;
|
|
||||||
// Create the result set
|
|
||||||
let mut res = Object::default();
|
|
||||||
// Process the tokens
|
|
||||||
let mut tmp = Object::default();
|
|
||||||
for v in run.all_sc_tokens(opt.ns(), opt.db(), sc).await?.iter() {
|
|
||||||
tmp.insert(v.name.to_string(), v.to_string().into());
|
|
||||||
}
|
|
||||||
res.insert("tokens".to_owned(), tmp.into());
|
|
||||||
// Ok all good
|
|
||||||
Value::from(res).ok()
|
|
||||||
}
|
|
||||||
InfoStatement::Tb(tb, false) => {
|
InfoStatement::Tb(tb, false) => {
|
||||||
// Allowed to run?
|
// Allowed to run?
|
||||||
opt.is_allowed(Action::View, ResourceKind::Any, &Base::Db)?;
|
opt.is_allowed(Action::View, ResourceKind::Any, &Base::Db)?;
|
||||||
|
@ -287,8 +257,11 @@ impl InfoStatement {
|
||||||
res.insert("databases".to_owned(), process_arr(run.all_db(opt.ns()).await?));
|
res.insert("databases".to_owned(), process_arr(run.all_db(opt.ns()).await?));
|
||||||
// Process the users
|
// Process the users
|
||||||
res.insert("users".to_owned(), process_arr(run.all_ns_users(opt.ns()).await?));
|
res.insert("users".to_owned(), process_arr(run.all_ns_users(opt.ns()).await?));
|
||||||
// Process the tokens
|
// Process the accesses
|
||||||
res.insert("tokens".to_owned(), process_arr(run.all_ns_tokens(opt.ns()).await?));
|
res.insert(
|
||||||
|
"accesses".to_owned(),
|
||||||
|
process_arr(run.all_ns_accesses(opt.ns()).await?),
|
||||||
|
);
|
||||||
// Ok all good
|
// Ok all good
|
||||||
Value::from(res).ok()
|
Value::from(res).ok()
|
||||||
}
|
}
|
||||||
|
@ -304,10 +277,10 @@ impl InfoStatement {
|
||||||
"users".to_owned(),
|
"users".to_owned(),
|
||||||
process_arr(run.all_db_users(opt.ns(), opt.db()).await?),
|
process_arr(run.all_db_users(opt.ns(), opt.db()).await?),
|
||||||
);
|
);
|
||||||
// Process the tokens
|
// Process the accesses
|
||||||
res.insert(
|
res.insert(
|
||||||
"tokens".to_owned(),
|
"accesses".to_owned(),
|
||||||
process_arr(run.all_db_tokens(opt.ns(), opt.db()).await?),
|
process_arr(run.all_db_accesses(opt.ns(), opt.db()).await?),
|
||||||
);
|
);
|
||||||
// Process the functions
|
// Process the functions
|
||||||
res.insert(
|
res.insert(
|
||||||
|
@ -324,8 +297,11 @@ impl InfoStatement {
|
||||||
"params".to_owned(),
|
"params".to_owned(),
|
||||||
process_arr(run.all_db_params(opt.ns(), opt.db()).await?),
|
process_arr(run.all_db_params(opt.ns(), opt.db()).await?),
|
||||||
);
|
);
|
||||||
// Process the scopes
|
// Process the accesses
|
||||||
res.insert("scopes".to_owned(), process_arr(run.all_sc(opt.ns(), opt.db()).await?));
|
res.insert(
|
||||||
|
"accesses".to_owned(),
|
||||||
|
process_arr(run.all_db_accesses(opt.ns(), opt.db()).await?),
|
||||||
|
);
|
||||||
// Process the tables
|
// Process the tables
|
||||||
res.insert("tables".to_owned(), process_arr(run.all_tb(opt.ns(), opt.db()).await?));
|
res.insert("tables".to_owned(), process_arr(run.all_tb(opt.ns(), opt.db()).await?));
|
||||||
// Process the analyzers
|
// Process the analyzers
|
||||||
|
@ -336,30 +312,6 @@ impl InfoStatement {
|
||||||
// Ok all good
|
// Ok all good
|
||||||
Value::from(res).ok()
|
Value::from(res).ok()
|
||||||
}
|
}
|
||||||
InfoStatement::Sc(sc, true) => {
|
|
||||||
// Allowed to run?
|
|
||||||
opt.is_allowed(Action::View, ResourceKind::Any, &Base::Db)?;
|
|
||||||
// Claim transaction
|
|
||||||
let mut run = txn.lock().await;
|
|
||||||
// Create the result set
|
|
||||||
let mut res = Object::default();
|
|
||||||
// Process the tokens
|
|
||||||
res.insert(
|
|
||||||
"tokens".to_owned(),
|
|
||||||
process_arr(run.all_sc_tokens(opt.ns(), opt.db(), sc).await?),
|
|
||||||
);
|
|
||||||
|
|
||||||
let def = run.get_sc(opt.ns(), opt.db(), sc).await?;
|
|
||||||
let Value::Object(o) = def.structure() else {
|
|
||||||
return Err(Error::Thrown(
|
|
||||||
"InfoStructure should return Value::Object".to_string(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
res.extend(o);
|
|
||||||
|
|
||||||
// Ok all good
|
|
||||||
Value::from(res).ok()
|
|
||||||
}
|
|
||||||
InfoStatement::Tb(tb, true) => {
|
InfoStatement::Tb(tb, true) => {
|
||||||
// Allowed to run?
|
// Allowed to run?
|
||||||
opt.is_allowed(Action::View, ResourceKind::Any, &Base::Db)?;
|
opt.is_allowed(Action::View, ResourceKind::Any, &Base::Db)?;
|
||||||
|
@ -425,8 +377,6 @@ impl fmt::Display for InfoStatement {
|
||||||
Self::Ns(true) => f.write_str("INFO FOR NAMESPACE STRUCTURE"),
|
Self::Ns(true) => f.write_str("INFO FOR NAMESPACE STRUCTURE"),
|
||||||
Self::Db(false) => f.write_str("INFO FOR DATABASE"),
|
Self::Db(false) => f.write_str("INFO FOR DATABASE"),
|
||||||
Self::Db(true) => f.write_str("INFO FOR DATABASE STRUCTURE"),
|
Self::Db(true) => f.write_str("INFO FOR DATABASE STRUCTURE"),
|
||||||
Self::Sc(ref s, false) => write!(f, "INFO FOR SCOPE {s}"),
|
|
||||||
Self::Sc(ref s, true) => write!(f, "INFO FOR SCOPE {s} STRUCTURE"),
|
|
||||||
Self::Tb(ref t, false) => write!(f, "INFO FOR TABLE {t}"),
|
Self::Tb(ref t, false) => write!(f, "INFO FOR TABLE {t}"),
|
||||||
Self::Tb(ref t, true) => write!(f, "INFO FOR TABLE {t} STRUCTURE"),
|
Self::Tb(ref t, true) => write!(f, "INFO FOR TABLE {t} STRUCTURE"),
|
||||||
Self::User(ref u, ref b, false) => match b {
|
Self::User(ref u, ref b, false) => match b {
|
||||||
|
@ -453,7 +403,6 @@ impl InfoStatement {
|
||||||
InfoStatement::Root(_) => InfoStatement::Root(true),
|
InfoStatement::Root(_) => InfoStatement::Root(true),
|
||||||
InfoStatement::Ns(_) => InfoStatement::Ns(true),
|
InfoStatement::Ns(_) => InfoStatement::Ns(true),
|
||||||
InfoStatement::Db(_) => InfoStatement::Db(true),
|
InfoStatement::Db(_) => InfoStatement::Db(true),
|
||||||
InfoStatement::Sc(s, _) => InfoStatement::Sc(s, true),
|
|
||||||
InfoStatement::Tb(t, _) => InfoStatement::Tb(t, true),
|
InfoStatement::Tb(t, _) => InfoStatement::Tb(t, true),
|
||||||
InfoStatement::User(u, b, _) => InfoStatement::User(u, b, true),
|
InfoStatement::User(u, b, _) => InfoStatement::User(u, b, true),
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,15 +52,15 @@ pub use self::throw::ThrowStatement;
|
||||||
pub use self::update::UpdateStatement;
|
pub use self::update::UpdateStatement;
|
||||||
|
|
||||||
pub use self::define::{
|
pub use self::define::{
|
||||||
DefineAnalyzerStatement, DefineDatabaseStatement, DefineEventStatement, DefineFieldStatement,
|
DefineAccessStatement, DefineAnalyzerStatement, DefineDatabaseStatement, DefineEventStatement,
|
||||||
DefineFunctionStatement, DefineIndexStatement, DefineModelStatement, DefineNamespaceStatement,
|
DefineFieldStatement, DefineFunctionStatement, DefineIndexStatement, DefineModelStatement,
|
||||||
DefineParamStatement, DefineScopeStatement, DefineStatement, DefineTableStatement,
|
DefineNamespaceStatement, DefineParamStatement, DefineStatement, DefineTableStatement,
|
||||||
DefineTokenStatement, DefineUserStatement,
|
DefineUserStatement,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use self::remove::{
|
pub use self::remove::{
|
||||||
RemoveAnalyzerStatement, RemoveDatabaseStatement, RemoveEventStatement, RemoveFieldStatement,
|
RemoveAccessStatement, RemoveAnalyzerStatement, RemoveDatabaseStatement, RemoveEventStatement,
|
||||||
RemoveFunctionStatement, RemoveIndexStatement, RemoveModelStatement, RemoveNamespaceStatement,
|
RemoveFieldStatement, RemoveFunctionStatement, RemoveIndexStatement, RemoveModelStatement,
|
||||||
RemoveParamStatement, RemoveScopeStatement, RemoveStatement, RemoveTableStatement,
|
RemoveNamespaceStatement, RemoveParamStatement, RemoveStatement, RemoveTableStatement,
|
||||||
RemoveTokenStatement, RemoveUserStatement,
|
RemoveUserStatement,
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,14 +12,14 @@ use std::fmt::{self, Display, Formatter};
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct RemoveTokenStatement {
|
pub struct RemoveAccessStatement {
|
||||||
pub name: Ident,
|
pub name: Ident,
|
||||||
pub base: Base,
|
pub base: Base,
|
||||||
#[revision(start = 2)]
|
#[revision(start = 2)]
|
||||||
pub if_exists: bool,
|
pub if_exists: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RemoveTokenStatement {
|
impl RemoveAccessStatement {
|
||||||
/// Process this type returning a computed simple Value
|
/// Process this type returning a computed simple Value
|
||||||
pub(crate) async fn compute(
|
pub(crate) async fn compute(
|
||||||
&self,
|
&self,
|
||||||
|
@ -38,9 +38,9 @@ impl RemoveTokenStatement {
|
||||||
// Clear the cache
|
// Clear the cache
|
||||||
run.clear_cache();
|
run.clear_cache();
|
||||||
// Get the definition
|
// Get the definition
|
||||||
let tk = run.get_ns_token(opt.ns(), &self.name).await?;
|
let ac = run.get_ns_access(opt.ns(), &self.name).await?;
|
||||||
// Delete the definition
|
// Delete the definition
|
||||||
let key = crate::key::namespace::tk::new(opt.ns(), &tk.name);
|
let key = crate::key::namespace::ac::new(opt.ns(), &ac.name);
|
||||||
run.del(key).await?;
|
run.del(key).await?;
|
||||||
// Ok all good
|
// Ok all good
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
|
@ -51,22 +51,9 @@ impl RemoveTokenStatement {
|
||||||
// Clear the cache
|
// Clear the cache
|
||||||
run.clear_cache();
|
run.clear_cache();
|
||||||
// Get the definition
|
// Get the definition
|
||||||
let tk = run.get_db_token(opt.ns(), opt.db(), &self.name).await?;
|
let ac = run.get_db_access(opt.ns(), opt.db(), &self.name).await?;
|
||||||
// Delete the definition
|
// Delete the definition
|
||||||
let key = crate::key::database::tk::new(opt.ns(), opt.db(), &tk.name);
|
let key = crate::key::database::ac::new(opt.ns(), opt.db(), &ac.name);
|
||||||
run.del(key).await?;
|
|
||||||
// Ok all good
|
|
||||||
Ok(Value::None)
|
|
||||||
}
|
|
||||||
Base::Sc(sc) => {
|
|
||||||
// Claim transaction
|
|
||||||
let mut run = txn.lock().await;
|
|
||||||
// Clear the cache
|
|
||||||
run.clear_cache();
|
|
||||||
// Get the definition
|
|
||||||
let tk = run.get_sc_token(opt.ns(), opt.db(), sc, &self.name).await?;
|
|
||||||
// Delete the definition
|
|
||||||
let key = crate::key::scope::tk::new(opt.ns(), opt.db(), sc, &tk.name);
|
|
||||||
run.del(key).await?;
|
run.del(key).await?;
|
||||||
// Ok all good
|
// Ok all good
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
|
@ -77,13 +64,10 @@ impl RemoveTokenStatement {
|
||||||
.await;
|
.await;
|
||||||
match future {
|
match future {
|
||||||
Err(e) if self.if_exists => match e {
|
Err(e) if self.if_exists => match e {
|
||||||
Error::NtNotFound {
|
Error::NaNotFound {
|
||||||
..
|
..
|
||||||
} => Ok(Value::None),
|
} => Ok(Value::None),
|
||||||
Error::DtNotFound {
|
Error::DaNotFound {
|
||||||
..
|
|
||||||
} => Ok(Value::None),
|
|
||||||
Error::StNotFound {
|
|
||||||
..
|
..
|
||||||
} => Ok(Value::None),
|
} => Ok(Value::None),
|
||||||
e => Err(e),
|
e => Err(e),
|
||||||
|
@ -93,9 +77,9 @@ impl RemoveTokenStatement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for RemoveTokenStatement {
|
impl Display for RemoveAccessStatement {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "REMOVE TOKEN")?;
|
write!(f, "REMOVE ACCESS")?;
|
||||||
if self.if_exists {
|
if self.if_exists {
|
||||||
write!(f, " IF EXISTS")?
|
write!(f, " IF EXISTS")?
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod access;
|
||||||
mod analyzer;
|
mod analyzer;
|
||||||
mod database;
|
mod database;
|
||||||
mod event;
|
mod event;
|
||||||
|
@ -7,11 +8,10 @@ mod index;
|
||||||
mod model;
|
mod model;
|
||||||
mod namespace;
|
mod namespace;
|
||||||
mod param;
|
mod param;
|
||||||
mod scope;
|
|
||||||
mod table;
|
mod table;
|
||||||
mod token;
|
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
|
pub use access::RemoveAccessStatement;
|
||||||
pub use analyzer::RemoveAnalyzerStatement;
|
pub use analyzer::RemoveAnalyzerStatement;
|
||||||
pub use database::RemoveDatabaseStatement;
|
pub use database::RemoveDatabaseStatement;
|
||||||
pub use event::RemoveEventStatement;
|
pub use event::RemoveEventStatement;
|
||||||
|
@ -21,9 +21,7 @@ pub use index::RemoveIndexStatement;
|
||||||
pub use model::RemoveModelStatement;
|
pub use model::RemoveModelStatement;
|
||||||
pub use namespace::RemoveNamespaceStatement;
|
pub use namespace::RemoveNamespaceStatement;
|
||||||
pub use param::RemoveParamStatement;
|
pub use param::RemoveParamStatement;
|
||||||
pub use scope::RemoveScopeStatement;
|
|
||||||
pub use table::RemoveTableStatement;
|
pub use table::RemoveTableStatement;
|
||||||
pub use token::RemoveTokenStatement;
|
|
||||||
pub use user::RemoveUserStatement;
|
pub use user::RemoveUserStatement;
|
||||||
|
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
|
@ -45,8 +43,7 @@ pub enum RemoveStatement {
|
||||||
Database(RemoveDatabaseStatement),
|
Database(RemoveDatabaseStatement),
|
||||||
Function(RemoveFunctionStatement),
|
Function(RemoveFunctionStatement),
|
||||||
Analyzer(RemoveAnalyzerStatement),
|
Analyzer(RemoveAnalyzerStatement),
|
||||||
Token(RemoveTokenStatement),
|
Access(RemoveAccessStatement),
|
||||||
Scope(RemoveScopeStatement),
|
|
||||||
Param(RemoveParamStatement),
|
Param(RemoveParamStatement),
|
||||||
Table(RemoveTableStatement),
|
Table(RemoveTableStatement),
|
||||||
Event(RemoveEventStatement),
|
Event(RemoveEventStatement),
|
||||||
|
@ -73,8 +70,7 @@ impl RemoveStatement {
|
||||||
Self::Namespace(ref v) => v.compute(ctx, opt, txn).await,
|
Self::Namespace(ref v) => v.compute(ctx, opt, txn).await,
|
||||||
Self::Database(ref v) => v.compute(ctx, opt, txn).await,
|
Self::Database(ref v) => v.compute(ctx, opt, txn).await,
|
||||||
Self::Function(ref v) => v.compute(ctx, opt, txn).await,
|
Self::Function(ref v) => v.compute(ctx, opt, txn).await,
|
||||||
Self::Token(ref v) => v.compute(ctx, opt, txn).await,
|
Self::Access(ref v) => v.compute(ctx, opt, txn).await,
|
||||||
Self::Scope(ref v) => v.compute(ctx, opt, txn).await,
|
|
||||||
Self::Param(ref v) => v.compute(ctx, opt, txn).await,
|
Self::Param(ref v) => v.compute(ctx, opt, txn).await,
|
||||||
Self::Table(ref v) => v.compute(ctx, opt, txn).await,
|
Self::Table(ref v) => v.compute(ctx, opt, txn).await,
|
||||||
Self::Event(ref v) => v.compute(ctx, opt, txn).await,
|
Self::Event(ref v) => v.compute(ctx, opt, txn).await,
|
||||||
|
@ -93,8 +89,7 @@ impl Display for RemoveStatement {
|
||||||
Self::Namespace(v) => Display::fmt(v, f),
|
Self::Namespace(v) => Display::fmt(v, f),
|
||||||
Self::Database(v) => Display::fmt(v, f),
|
Self::Database(v) => Display::fmt(v, f),
|
||||||
Self::Function(v) => Display::fmt(v, f),
|
Self::Function(v) => Display::fmt(v, f),
|
||||||
Self::Token(v) => Display::fmt(v, f),
|
Self::Access(v) => Display::fmt(v, f),
|
||||||
Self::Scope(v) => Display::fmt(v, f),
|
|
||||||
Self::Param(v) => Display::fmt(v, f),
|
Self::Param(v) => Display::fmt(v, f),
|
||||||
Self::Table(v) => Display::fmt(v, f),
|
Self::Table(v) => Display::fmt(v, f),
|
||||||
Self::Event(v) => Display::fmt(v, f),
|
Self::Event(v) => Display::fmt(v, f),
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::{Options, Transaction};
|
|
||||||
use crate::err::Error;
|
|
||||||
use crate::iam::{Action, ResourceKind};
|
|
||||||
use crate::sql::{Base, Ident, Value};
|
|
||||||
use derive::Store;
|
|
||||||
use revision::revisioned;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
|
||||||
|
|
||||||
#[revisioned(revision = 2)]
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct RemoveScopeStatement {
|
|
||||||
pub name: Ident,
|
|
||||||
#[revision(start = 2)]
|
|
||||||
pub if_exists: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RemoveScopeStatement {
|
|
||||||
/// Process this type returning a computed simple Value
|
|
||||||
pub(crate) async fn compute(
|
|
||||||
&self,
|
|
||||||
_ctx: &Context<'_>,
|
|
||||||
opt: &Options,
|
|
||||||
txn: &Transaction,
|
|
||||||
) -> Result<Value, Error> {
|
|
||||||
let future = async {
|
|
||||||
// Allowed to run?
|
|
||||||
opt.is_allowed(Action::Edit, ResourceKind::Scope, &Base::Db)?;
|
|
||||||
// Claim transaction
|
|
||||||
let mut run = txn.lock().await;
|
|
||||||
// Clear the cache
|
|
||||||
run.clear_cache();
|
|
||||||
// Get the definition
|
|
||||||
let sc = run.get_sc(opt.ns(), opt.db(), &self.name).await?;
|
|
||||||
// Delete the definition
|
|
||||||
let key = crate::key::database::sc::new(opt.ns(), opt.db(), &sc.name);
|
|
||||||
run.del(key).await?;
|
|
||||||
// Remove the resource data
|
|
||||||
let key = crate::key::scope::all::new(opt.ns(), opt.db(), &sc.name);
|
|
||||||
run.delp(key, u32::MAX).await?;
|
|
||||||
// Ok all good
|
|
||||||
Ok(Value::None)
|
|
||||||
}
|
|
||||||
.await;
|
|
||||||
match future {
|
|
||||||
Err(Error::ScNotFound {
|
|
||||||
..
|
|
||||||
}) if self.if_exists => Ok(Value::None),
|
|
||||||
v => v,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for RemoveScopeStatement {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(f, "REMOVE SCOPE")?;
|
|
||||||
if self.if_exists {
|
|
||||||
write!(f, " IF EXISTS")?
|
|
||||||
}
|
|
||||||
write!(f, " {}", self.name)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
2
core/src/sql/value/serde/ser/access/mod.rs
Normal file
2
core/src/sql/value/serde/ser/access/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub(super) mod opt;
|
||||||
|
pub(super) mod vec;
|
56
core/src/sql/value/serde/ser/access/opt.rs
Normal file
56
core/src/sql/value/serde/ser/access/opt.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::sql::value::serde::ser;
|
||||||
|
use crate::sql::Access;
|
||||||
|
use serde::ser::Impossible;
|
||||||
|
use serde::ser::Serialize;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Serializer;
|
||||||
|
|
||||||
|
impl ser::Serializer for Serializer {
|
||||||
|
type Ok = Option<Access>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<Option<Access>, Error>;
|
||||||
|
type SerializeTuple = Impossible<Option<Access>, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<Option<Access>, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<Option<Access>, Error>;
|
||||||
|
type SerializeMap = Impossible<Option<Access>, Error>;
|
||||||
|
type SerializeStruct = Impossible<Option<Access>, Error>;
|
||||||
|
type SerializeStructVariant = Impossible<Option<Access>, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "an `Option<Access>`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
Ok(Some(Access(value.serialize(ser::string::Serializer.wrap())?)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ser::Serializer as _;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn none() {
|
||||||
|
let option: Option<Access> = None;
|
||||||
|
let serialized = option.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(option, serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn some() {
|
||||||
|
let option = Some(Access::default());
|
||||||
|
let serialized = option.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(option, serialized);
|
||||||
|
}
|
||||||
|
}
|
79
core/src/sql/value/serde/ser/access/vec.rs
Normal file
79
core/src/sql/value/serde/ser/access/vec.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::sql::value::serde::ser;
|
||||||
|
use crate::sql::Access;
|
||||||
|
use ser::Serializer as _;
|
||||||
|
use serde::ser::Impossible;
|
||||||
|
use serde::ser::Serialize;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Serializer;
|
||||||
|
|
||||||
|
impl ser::Serializer for Serializer {
|
||||||
|
type Ok = Vec<Access>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = SerializeAccessVec;
|
||||||
|
type SerializeTuple = Impossible<Vec<Access>, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<Vec<Access>, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<Vec<Access>, Error>;
|
||||||
|
type SerializeMap = Impossible<Vec<Access>, Error>;
|
||||||
|
type SerializeStruct = Impossible<Vec<Access>, Error>;
|
||||||
|
type SerializeStructVariant = Impossible<Vec<Access>, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "a `Access` sequence";
|
||||||
|
|
||||||
|
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Error> {
|
||||||
|
Ok(SerializeAccessVec(Vec::with_capacity(len.unwrap_or_default())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_newtype_struct<T>(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
value.serialize(self.wrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct SerializeAccessVec(Vec<Access>);
|
||||||
|
|
||||||
|
impl serde::ser::SerializeSeq for SerializeAccessVec {
|
||||||
|
type Ok = Vec<Access>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
T: Serialize + ?Sized,
|
||||||
|
{
|
||||||
|
self.0.push(Access(value.serialize(ser::string::Serializer.wrap())?));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
Ok(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
let vec: Vec<Access> = Vec::new();
|
||||||
|
let serialized = vec.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(vec, serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec() {
|
||||||
|
let vec = vec![Access("foo".to_owned())];
|
||||||
|
let serialized = vec.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(vec, serialized);
|
||||||
|
}
|
||||||
|
}
|
459
core/src/sql/value/serde/ser/access_type/mod.rs
Normal file
459
core/src/sql/value/serde/ser/access_type/mod.rs
Normal file
|
@ -0,0 +1,459 @@
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::sql::access_type::{
|
||||||
|
AccessType, JwtAccess, JwtAccessIssue, JwtAccessVerify, JwtAccessVerifyJwks,
|
||||||
|
JwtAccessVerifyKey, RecordAccess,
|
||||||
|
};
|
||||||
|
use crate::sql::value::serde::ser;
|
||||||
|
use crate::sql::Algorithm;
|
||||||
|
use crate::sql::Duration;
|
||||||
|
use crate::sql::Value;
|
||||||
|
use ser::Serializer as _;
|
||||||
|
use serde::ser::Error as _;
|
||||||
|
use serde::ser::Impossible;
|
||||||
|
use serde::ser::Serialize;
|
||||||
|
|
||||||
|
// Serialize Access Method
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Serializer;
|
||||||
|
|
||||||
|
impl ser::Serializer for Serializer {
|
||||||
|
type Ok = AccessType;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<AccessType, Error>;
|
||||||
|
type SerializeTuple = Impossible<AccessType, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<AccessType, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<AccessType, Error>;
|
||||||
|
type SerializeMap = Impossible<AccessType, Error>;
|
||||||
|
type SerializeStruct = Impossible<AccessType, Error>;
|
||||||
|
type SerializeStructVariant = Impossible<AccessType, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "a `AccessType`";
|
||||||
|
|
||||||
|
fn serialize_newtype_variant<T>(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match variant {
|
||||||
|
"Record" => Ok(AccessType::Record(value.serialize(SerializerRecord.wrap())?)),
|
||||||
|
"Jwt" => Ok(AccessType::Jwt(value.serialize(SerializerJwt.wrap())?)),
|
||||||
|
variant => {
|
||||||
|
Err(Error::custom(format!("unexpected newtype variant `{name}::{variant}`")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize Record Access
|
||||||
|
|
||||||
|
pub struct SerializerRecord;
|
||||||
|
|
||||||
|
impl ser::Serializer for SerializerRecord {
|
||||||
|
type Ok = RecordAccess;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<RecordAccess, Error>;
|
||||||
|
type SerializeTuple = Impossible<RecordAccess, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<RecordAccess, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<RecordAccess, Error>;
|
||||||
|
type SerializeMap = Impossible<RecordAccess, Error>;
|
||||||
|
type SerializeStruct = SerializeRecord;
|
||||||
|
type SerializeStructVariant = Impossible<RecordAccess, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "a struct `RecordAccess`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_struct(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeStruct, Error> {
|
||||||
|
Ok(SerializeRecord::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct SerializeRecord {
|
||||||
|
pub duration: Option<Duration>,
|
||||||
|
pub signup: Option<Value>,
|
||||||
|
pub signin: Option<Value>,
|
||||||
|
pub jwt: JwtAccess,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeStruct for SerializeRecord {
|
||||||
|
type Ok = RecordAccess;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match key {
|
||||||
|
"duration" => {
|
||||||
|
self.duration =
|
||||||
|
value.serialize(ser::duration::opt::Serializer.wrap())?.map(Into::into);
|
||||||
|
}
|
||||||
|
"signup" => {
|
||||||
|
self.signup = value.serialize(ser::value::opt::Serializer.wrap())?;
|
||||||
|
}
|
||||||
|
"signin" => {
|
||||||
|
self.signin = value.serialize(ser::value::opt::Serializer.wrap())?;
|
||||||
|
}
|
||||||
|
"jwt" => {
|
||||||
|
self.jwt = value.serialize(SerializerJwt.wrap())?;
|
||||||
|
}
|
||||||
|
key => {
|
||||||
|
return Err(Error::custom(format!("unexpected field `RecordAccess::{key}`")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Error> {
|
||||||
|
Ok(RecordAccess {
|
||||||
|
duration: self.duration,
|
||||||
|
signup: self.signup,
|
||||||
|
signin: self.signin,
|
||||||
|
jwt: self.jwt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize JWT Access
|
||||||
|
|
||||||
|
pub struct SerializerJwt;
|
||||||
|
|
||||||
|
impl ser::Serializer for SerializerJwt {
|
||||||
|
type Ok = JwtAccess;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<JwtAccess, Error>;
|
||||||
|
type SerializeTuple = Impossible<JwtAccess, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<JwtAccess, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<JwtAccess, Error>;
|
||||||
|
type SerializeMap = Impossible<JwtAccess, Error>;
|
||||||
|
type SerializeStruct = SerializeJwt;
|
||||||
|
type SerializeStructVariant = Impossible<JwtAccess, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "a struct `JwtAccess`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_struct(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeStruct, Error> {
|
||||||
|
Ok(SerializeJwt::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct SerializeJwt {
|
||||||
|
pub verify: JwtAccessVerify,
|
||||||
|
pub issue: Option<JwtAccessIssue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeStruct for SerializeJwt {
|
||||||
|
type Ok = JwtAccess;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match key {
|
||||||
|
"verify" => {
|
||||||
|
self.verify = value.serialize(SerializerJwtVerify.wrap())?;
|
||||||
|
}
|
||||||
|
"issue" => {
|
||||||
|
self.issue = value.serialize(SerializerJwtIssueOpt.wrap())?;
|
||||||
|
}
|
||||||
|
key => {
|
||||||
|
return Err(Error::custom(format!("unexpected field `JwtAccess::{key}`")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Error> {
|
||||||
|
Ok(JwtAccess {
|
||||||
|
verify: self.verify,
|
||||||
|
issue: self.issue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize JWT Access Verify
|
||||||
|
|
||||||
|
pub struct SerializerJwtVerify;
|
||||||
|
|
||||||
|
impl ser::Serializer for SerializerJwtVerify {
|
||||||
|
type Ok = JwtAccessVerify;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<JwtAccessVerify, Error>;
|
||||||
|
type SerializeTuple = Impossible<JwtAccessVerify, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<JwtAccessVerify, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<JwtAccessVerify, Error>;
|
||||||
|
type SerializeMap = Impossible<JwtAccessVerify, Error>;
|
||||||
|
type SerializeStruct = Impossible<JwtAccessVerify, Error>;
|
||||||
|
type SerializeStructVariant = Impossible<JwtAccessVerify, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "a `JwtAccessVerify`";
|
||||||
|
|
||||||
|
fn serialize_newtype_variant<T>(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match variant {
|
||||||
|
"Key" => Ok(JwtAccessVerify::Key(value.serialize(SerializerJwtVerifyKey.wrap())?)),
|
||||||
|
"Jwks" => Ok(JwtAccessVerify::Jwks(value.serialize(SerializerJwtVerifyJwks.wrap())?)),
|
||||||
|
variant => {
|
||||||
|
Err(Error::custom(format!("unexpected newtype variant `{name}::{variant}`")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize JWT Access Verify Key
|
||||||
|
|
||||||
|
pub struct SerializerJwtVerifyKey;
|
||||||
|
|
||||||
|
impl ser::Serializer for SerializerJwtVerifyKey {
|
||||||
|
type Ok = JwtAccessVerifyKey;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<JwtAccessVerifyKey, Error>;
|
||||||
|
type SerializeTuple = Impossible<JwtAccessVerifyKey, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<JwtAccessVerifyKey, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<JwtAccessVerifyKey, Error>;
|
||||||
|
type SerializeMap = Impossible<JwtAccessVerifyKey, Error>;
|
||||||
|
type SerializeStruct = SerializeJwtVerifyKey;
|
||||||
|
type SerializeStructVariant = Impossible<JwtAccessVerifyKey, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "a struct `JwtAccessVerifyKey`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_struct(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeStruct, Error> {
|
||||||
|
Ok(SerializeJwtVerifyKey::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct SerializeJwtVerifyKey {
|
||||||
|
pub alg: Algorithm,
|
||||||
|
pub key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeStruct for SerializeJwtVerifyKey {
|
||||||
|
type Ok = JwtAccessVerifyKey;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match key {
|
||||||
|
"alg" => {
|
||||||
|
self.alg = value.serialize(ser::algorithm::Serializer.wrap())?;
|
||||||
|
}
|
||||||
|
"key" => {
|
||||||
|
self.key = value.serialize(ser::string::Serializer.wrap())?;
|
||||||
|
}
|
||||||
|
key => {
|
||||||
|
return Err(Error::custom(format!("unexpected field `JwtAccessVerifyKey::{key}`")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Error> {
|
||||||
|
Ok(JwtAccessVerifyKey {
|
||||||
|
alg: self.alg,
|
||||||
|
key: self.key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize JWT Access Verify JWKS
|
||||||
|
|
||||||
|
pub struct SerializerJwtVerifyJwks;
|
||||||
|
|
||||||
|
impl ser::Serializer for SerializerJwtVerifyJwks {
|
||||||
|
type Ok = JwtAccessVerifyJwks;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<JwtAccessVerifyJwks, Error>;
|
||||||
|
type SerializeTuple = Impossible<JwtAccessVerifyJwks, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<JwtAccessVerifyJwks, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<JwtAccessVerifyJwks, Error>;
|
||||||
|
type SerializeMap = Impossible<JwtAccessVerifyJwks, Error>;
|
||||||
|
type SerializeStruct = SerializeJwtVerifyJwks;
|
||||||
|
type SerializeStructVariant = Impossible<JwtAccessVerifyJwks, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "a struct `JwtAccessVerifyJwks`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_struct(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeStruct, Error> {
|
||||||
|
Ok(SerializeJwtVerifyJwks::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct SerializeJwtVerifyJwks {
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeStruct for SerializeJwtVerifyJwks {
|
||||||
|
type Ok = JwtAccessVerifyJwks;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match key {
|
||||||
|
"url" => {
|
||||||
|
self.url = value.serialize(ser::string::Serializer.wrap())?;
|
||||||
|
}
|
||||||
|
key => {
|
||||||
|
return Err(Error::custom(format!(
|
||||||
|
"unexpected field `JwtAccessVerifyJwks::{key}`"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Error> {
|
||||||
|
Ok(JwtAccessVerifyJwks {
|
||||||
|
url: self.url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize JWT Access Issue
|
||||||
|
|
||||||
|
pub struct SerializerJwtIssueOpt;
|
||||||
|
|
||||||
|
impl ser::Serializer for SerializerJwtIssueOpt {
|
||||||
|
type Ok = Option<JwtAccessIssue>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<Option<JwtAccessIssue>, Error>;
|
||||||
|
type SerializeTuple = Impossible<Option<JwtAccessIssue>, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<Option<JwtAccessIssue>, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<Option<JwtAccessIssue>, Error>;
|
||||||
|
type SerializeMap = Impossible<Option<JwtAccessIssue>, Error>;
|
||||||
|
type SerializeStruct = Impossible<Option<JwtAccessIssue>, Error>;
|
||||||
|
type SerializeStructVariant = Impossible<Option<JwtAccessIssue>, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "an `Option<JwtAccessIssue>`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
Ok(Some(value.serialize(SerializerJwtIssue.wrap())?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SerializerJwtIssue;
|
||||||
|
|
||||||
|
impl ser::Serializer for SerializerJwtIssue {
|
||||||
|
type Ok = JwtAccessIssue;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<JwtAccessIssue, Error>;
|
||||||
|
type SerializeTuple = Impossible<JwtAccessIssue, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<JwtAccessIssue, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<JwtAccessIssue, Error>;
|
||||||
|
type SerializeMap = Impossible<JwtAccessIssue, Error>;
|
||||||
|
type SerializeStruct = SerializeJwtIssue;
|
||||||
|
type SerializeStructVariant = Impossible<JwtAccessIssue, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "a struct `JwtAccessIssue`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_struct(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeStruct, Error> {
|
||||||
|
Ok(SerializeJwtIssue::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct SerializeJwtIssue {
|
||||||
|
pub alg: Algorithm,
|
||||||
|
pub key: String,
|
||||||
|
pub duration: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeStruct for SerializeJwtIssue {
|
||||||
|
type Ok = JwtAccessIssue;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match key {
|
||||||
|
"alg" => {
|
||||||
|
self.alg = value.serialize(ser::algorithm::Serializer.wrap())?;
|
||||||
|
}
|
||||||
|
"key" => {
|
||||||
|
self.key = value.serialize(ser::string::Serializer.wrap())?;
|
||||||
|
}
|
||||||
|
"duration" => {
|
||||||
|
self.duration =
|
||||||
|
value.serialize(ser::duration::opt::Serializer.wrap())?.map(Into::into);
|
||||||
|
}
|
||||||
|
key => {
|
||||||
|
return Err(Error::custom(format!("unexpected field `JwtAccessIssue::{key}`")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Error> {
|
||||||
|
Ok(JwtAccessIssue {
|
||||||
|
duration: self.duration,
|
||||||
|
alg: self.alg,
|
||||||
|
key: self.key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod access_type;
|
||||||
mod algorithm;
|
mod algorithm;
|
||||||
mod base;
|
mod base;
|
||||||
mod block;
|
mod block;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql::statements::DefineTokenStatement;
|
use crate::sql::access_type::AccessType;
|
||||||
|
use crate::sql::statements::DefineAccessStatement;
|
||||||
use crate::sql::value::serde::ser;
|
use crate::sql::value::serde::ser;
|
||||||
use crate::sql::Algorithm;
|
|
||||||
use crate::sql::Base;
|
use crate::sql::Base;
|
||||||
use crate::sql::Ident;
|
use crate::sql::Ident;
|
||||||
use crate::sql::Strand;
|
use crate::sql::Strand;
|
||||||
|
@ -14,18 +14,18 @@ use serde::ser::Serialize;
|
||||||
pub struct Serializer;
|
pub struct Serializer;
|
||||||
|
|
||||||
impl ser::Serializer for Serializer {
|
impl ser::Serializer for Serializer {
|
||||||
type Ok = DefineTokenStatement;
|
type Ok = DefineAccessStatement;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
type SerializeSeq = Impossible<DefineTokenStatement, Error>;
|
type SerializeSeq = Impossible<DefineAccessStatement, Error>;
|
||||||
type SerializeTuple = Impossible<DefineTokenStatement, Error>;
|
type SerializeTuple = Impossible<DefineAccessStatement, Error>;
|
||||||
type SerializeTupleStruct = Impossible<DefineTokenStatement, Error>;
|
type SerializeTupleStruct = Impossible<DefineAccessStatement, Error>;
|
||||||
type SerializeTupleVariant = Impossible<DefineTokenStatement, Error>;
|
type SerializeTupleVariant = Impossible<DefineAccessStatement, Error>;
|
||||||
type SerializeMap = Impossible<DefineTokenStatement, Error>;
|
type SerializeMap = Impossible<DefineAccessStatement, Error>;
|
||||||
type SerializeStruct = SerializeDefineTokenStatement;
|
type SerializeStruct = SerializeDefineAccessStatement;
|
||||||
type SerializeStructVariant = Impossible<DefineTokenStatement, Error>;
|
type SerializeStructVariant = Impossible<DefineAccessStatement, Error>;
|
||||||
|
|
||||||
const EXPECTED: &'static str = "a struct `DefineTokenStatement`";
|
const EXPECTED: &'static str = "a struct `DefineAccessStatement`";
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn serialize_struct(
|
fn serialize_struct(
|
||||||
|
@ -33,23 +33,22 @@ impl ser::Serializer for Serializer {
|
||||||
_name: &'static str,
|
_name: &'static str,
|
||||||
_len: usize,
|
_len: usize,
|
||||||
) -> Result<Self::SerializeStruct, Error> {
|
) -> Result<Self::SerializeStruct, Error> {
|
||||||
Ok(SerializeDefineTokenStatement::default())
|
Ok(SerializeDefineAccessStatement::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct SerializeDefineTokenStatement {
|
pub struct SerializeDefineAccessStatement {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
base: Base,
|
base: Base,
|
||||||
kind: Algorithm,
|
kind: AccessType,
|
||||||
code: String,
|
|
||||||
comment: Option<Strand>,
|
comment: Option<Strand>,
|
||||||
if_not_exists: bool,
|
if_not_exists: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl serde::ser::SerializeStruct for SerializeDefineTokenStatement {
|
impl serde::ser::SerializeStruct for SerializeDefineAccessStatement {
|
||||||
type Ok = DefineTokenStatement;
|
type Ok = DefineAccessStatement;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||||
|
@ -64,10 +63,7 @@ impl serde::ser::SerializeStruct for SerializeDefineTokenStatement {
|
||||||
self.base = value.serialize(ser::base::Serializer.wrap())?;
|
self.base = value.serialize(ser::base::Serializer.wrap())?;
|
||||||
}
|
}
|
||||||
"kind" => {
|
"kind" => {
|
||||||
self.kind = value.serialize(ser::algorithm::Serializer.wrap())?;
|
self.kind = value.serialize(ser::access_type::Serializer.wrap())?;
|
||||||
}
|
|
||||||
"code" => {
|
|
||||||
self.code = value.serialize(ser::string::Serializer.wrap())?;
|
|
||||||
}
|
}
|
||||||
"comment" => {
|
"comment" => {
|
||||||
self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?;
|
self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?;
|
||||||
|
@ -77,7 +73,7 @@ impl serde::ser::SerializeStruct for SerializeDefineTokenStatement {
|
||||||
}
|
}
|
||||||
key => {
|
key => {
|
||||||
return Err(Error::custom(format!(
|
return Err(Error::custom(format!(
|
||||||
"unexpected field `DefineTokenStatement::{key}`"
|
"unexpected field `DefineAccessStatement::{key}`"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,11 +81,10 @@ impl serde::ser::SerializeStruct for SerializeDefineTokenStatement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end(self) -> Result<Self::Ok, Error> {
|
fn end(self) -> Result<Self::Ok, Error> {
|
||||||
Ok(DefineTokenStatement {
|
Ok(DefineAccessStatement {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
base: self.base,
|
base: self.base,
|
||||||
kind: self.kind,
|
kind: self.kind,
|
||||||
code: self.code,
|
|
||||||
comment: self.comment,
|
comment: self.comment,
|
||||||
if_not_exists: self.if_not_exists,
|
if_not_exists: self.if_not_exists,
|
||||||
})
|
})
|
||||||
|
@ -102,8 +97,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default() {
|
fn default() {
|
||||||
let stmt = DefineTokenStatement::default();
|
let stmt = DefineAccessStatement::default();
|
||||||
let value: DefineTokenStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
let value: DefineAccessStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||||
assert_eq!(value, stmt);
|
assert_eq!(value, stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod access;
|
||||||
mod analyzer;
|
mod analyzer;
|
||||||
mod database;
|
mod database;
|
||||||
mod event;
|
mod event;
|
||||||
|
@ -6,9 +7,7 @@ mod function;
|
||||||
mod index;
|
mod index;
|
||||||
mod namespace;
|
mod namespace;
|
||||||
mod param;
|
mod param;
|
||||||
mod scope;
|
|
||||||
mod table;
|
mod table;
|
||||||
mod token;
|
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
|
@ -59,8 +58,7 @@ impl ser::Serializer for Serializer {
|
||||||
"Analyzer" => {
|
"Analyzer" => {
|
||||||
Ok(DefineStatement::Analyzer(value.serialize(analyzer::Serializer.wrap())?))
|
Ok(DefineStatement::Analyzer(value.serialize(analyzer::Serializer.wrap())?))
|
||||||
}
|
}
|
||||||
"Token" => Ok(DefineStatement::Token(value.serialize(token::Serializer.wrap())?)),
|
"Access" => Ok(DefineStatement::Access(value.serialize(access::Serializer.wrap())?)),
|
||||||
"Scope" => Ok(DefineStatement::Scope(value.serialize(scope::Serializer.wrap())?)),
|
|
||||||
"Param" => Ok(DefineStatement::Param(value.serialize(param::Serializer.wrap())?)),
|
"Param" => Ok(DefineStatement::Param(value.serialize(param::Serializer.wrap())?)),
|
||||||
"Table" => Ok(DefineStatement::Table(value.serialize(table::Serializer.wrap())?)),
|
"Table" => Ok(DefineStatement::Table(value.serialize(table::Serializer.wrap())?)),
|
||||||
"Event" => Ok(DefineStatement::Event(value.serialize(event::Serializer.wrap())?)),
|
"Event" => Ok(DefineStatement::Event(value.serialize(event::Serializer.wrap())?)),
|
||||||
|
@ -108,15 +106,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn token() {
|
fn access() {
|
||||||
let stmt = DefineStatement::Token(Default::default());
|
let stmt = DefineStatement::Access(Default::default());
|
||||||
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
|
||||||
assert_eq!(stmt, serialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn scope() {
|
|
||||||
let stmt = DefineStatement::Scope(Default::default());
|
|
||||||
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
||||||
assert_eq!(stmt, serialized);
|
assert_eq!(stmt, serialized);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,115 +0,0 @@
|
||||||
use crate::err::Error;
|
|
||||||
use crate::sql::statements::DefineScopeStatement;
|
|
||||||
use crate::sql::value::serde::ser;
|
|
||||||
use crate::sql::Duration;
|
|
||||||
use crate::sql::Ident;
|
|
||||||
use crate::sql::Strand;
|
|
||||||
use crate::sql::Value;
|
|
||||||
use ser::Serializer as _;
|
|
||||||
use serde::ser::Error as _;
|
|
||||||
use serde::ser::Impossible;
|
|
||||||
use serde::ser::Serialize;
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Serializer;
|
|
||||||
|
|
||||||
impl ser::Serializer for Serializer {
|
|
||||||
type Ok = DefineScopeStatement;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
type SerializeSeq = Impossible<DefineScopeStatement, Error>;
|
|
||||||
type SerializeTuple = Impossible<DefineScopeStatement, Error>;
|
|
||||||
type SerializeTupleStruct = Impossible<DefineScopeStatement, Error>;
|
|
||||||
type SerializeTupleVariant = Impossible<DefineScopeStatement, Error>;
|
|
||||||
type SerializeMap = Impossible<DefineScopeStatement, Error>;
|
|
||||||
type SerializeStruct = SerializeDefineScopeStatement;
|
|
||||||
type SerializeStructVariant = Impossible<DefineScopeStatement, Error>;
|
|
||||||
|
|
||||||
const EXPECTED: &'static str = "a struct `DefineScopeStatement`";
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn serialize_struct(
|
|
||||||
self,
|
|
||||||
_name: &'static str,
|
|
||||||
_len: usize,
|
|
||||||
) -> Result<Self::SerializeStruct, Error> {
|
|
||||||
Ok(SerializeDefineScopeStatement::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct SerializeDefineScopeStatement {
|
|
||||||
name: Ident,
|
|
||||||
code: String,
|
|
||||||
session: Option<Duration>,
|
|
||||||
signup: Option<Value>,
|
|
||||||
signin: Option<Value>,
|
|
||||||
comment: Option<Strand>,
|
|
||||||
if_not_exists: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl serde::ser::SerializeStruct for SerializeDefineScopeStatement {
|
|
||||||
type Ok = DefineScopeStatement;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
T: ?Sized + Serialize,
|
|
||||||
{
|
|
||||||
match key {
|
|
||||||
"name" => {
|
|
||||||
self.name = Ident(value.serialize(ser::string::Serializer.wrap())?);
|
|
||||||
}
|
|
||||||
"code" => {
|
|
||||||
self.code = value.serialize(ser::string::Serializer.wrap())?;
|
|
||||||
}
|
|
||||||
"session" => {
|
|
||||||
self.session =
|
|
||||||
value.serialize(ser::duration::opt::Serializer.wrap())?.map(Into::into);
|
|
||||||
}
|
|
||||||
"signup" => {
|
|
||||||
self.signup = value.serialize(ser::value::opt::Serializer.wrap())?;
|
|
||||||
}
|
|
||||||
"signin" => {
|
|
||||||
self.signin = value.serialize(ser::value::opt::Serializer.wrap())?;
|
|
||||||
}
|
|
||||||
"comment" => {
|
|
||||||
self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?;
|
|
||||||
}
|
|
||||||
"if_not_exists" => {
|
|
||||||
self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?
|
|
||||||
}
|
|
||||||
key => {
|
|
||||||
return Err(Error::custom(format!(
|
|
||||||
"unexpected field `DefineScopeStatement::{key}`"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end(self) -> Result<Self::Ok, Error> {
|
|
||||||
Ok(DefineScopeStatement {
|
|
||||||
name: self.name,
|
|
||||||
code: self.code,
|
|
||||||
session: self.session,
|
|
||||||
signup: self.signup,
|
|
||||||
signin: self.signin,
|
|
||||||
comment: self.comment,
|
|
||||||
if_not_exists: self.if_not_exists,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn default() {
|
|
||||||
let stmt = DefineScopeStatement::default();
|
|
||||||
let value: DefineScopeStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
|
||||||
assert_eq!(value, stmt);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -61,7 +61,6 @@ impl ser::Serializer for Serializer {
|
||||||
_len: usize,
|
_len: usize,
|
||||||
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
||||||
match variant {
|
match variant {
|
||||||
"Sc" => Ok(SerializeInfoStatement::with(Which::Sc)),
|
|
||||||
"Tb" => Ok(SerializeInfoStatement::with(Which::Tb)),
|
"Tb" => Ok(SerializeInfoStatement::with(Which::Tb)),
|
||||||
"User" => Ok(SerializeInfoStatement::with(Which::User)),
|
"User" => Ok(SerializeInfoStatement::with(Which::User)),
|
||||||
variant => Err(Error::custom(format!("unexpected tuple variant `{name}::{variant}`"))),
|
variant => Err(Error::custom(format!("unexpected tuple variant `{name}::{variant}`"))),
|
||||||
|
@ -71,7 +70,6 @@ impl ser::Serializer for Serializer {
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum Which {
|
enum Which {
|
||||||
Sc,
|
|
||||||
Tb,
|
Tb,
|
||||||
User,
|
User,
|
||||||
}
|
}
|
||||||
|
@ -79,9 +77,6 @@ enum Which {
|
||||||
impl Display for Which {
|
impl Display for Which {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Which::Sc => {
|
|
||||||
write!(f, "Sc")
|
|
||||||
}
|
|
||||||
Which::Tb => {
|
Which::Tb => {
|
||||||
write!(f, "Tb")
|
write!(f, "Tb")
|
||||||
}
|
}
|
||||||
|
@ -121,7 +116,7 @@ impl serde::ser::SerializeTupleVariant for SerializeInfoStatement {
|
||||||
(_, 0) => {
|
(_, 0) => {
|
||||||
self.tuple.0 = Some(Ident(value.serialize(ser::string::Serializer.wrap())?));
|
self.tuple.0 = Some(Ident(value.serialize(ser::string::Serializer.wrap())?));
|
||||||
}
|
}
|
||||||
(Sc, 1) | (Tb, 1) => {
|
(Tb, 1) => {
|
||||||
self.tuple.2 = value.serialize(ser::primitive::bool::Serializer.wrap())?;
|
self.tuple.2 = value.serialize(ser::primitive::bool::Serializer.wrap())?;
|
||||||
}
|
}
|
||||||
(User, 1) => {
|
(User, 1) => {
|
||||||
|
@ -144,8 +139,6 @@ impl serde::ser::SerializeTupleVariant for SerializeInfoStatement {
|
||||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
use Which::*;
|
use Which::*;
|
||||||
match (self.which, self.tuple.0) {
|
match (self.which, self.tuple.0) {
|
||||||
(Sc, Some(ident)) => Ok(InfoStatement::Sc(ident, self.tuple.2)),
|
|
||||||
(Sc, None) => Err(Error::custom("`InfoStatement::Sc` missing required value(s)")),
|
|
||||||
(Tb, Some(ident)) => Ok(InfoStatement::Tb(ident, self.tuple.2)),
|
(Tb, Some(ident)) => Ok(InfoStatement::Tb(ident, self.tuple.2)),
|
||||||
(Tb, None) => Err(Error::custom("`InfoStatement::Tb` missing required value(s)")),
|
(Tb, None) => Err(Error::custom("`InfoStatement::Tb` missing required value(s)")),
|
||||||
(User, Some(ident)) => Ok(InfoStatement::User(ident, self.tuple.1, self.tuple.2)),
|
(User, Some(ident)) => Ok(InfoStatement::User(ident, self.tuple.1, self.tuple.2)),
|
||||||
|
@ -179,13 +172,6 @@ mod tests {
|
||||||
assert_eq!(stmt, serialized);
|
assert_eq!(stmt, serialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sc() {
|
|
||||||
let stmt = InfoStatement::Sc(Default::default(), Default::default());
|
|
||||||
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
|
||||||
assert_eq!(stmt, serialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tb() {
|
fn tb() {
|
||||||
let stmt = InfoStatement::Tb(Default::default(), Default::default());
|
let stmt = InfoStatement::Tb(Default::default(), Default::default());
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql::statements::RemoveTokenStatement;
|
use crate::sql::statements::RemoveAccessStatement;
|
||||||
use crate::sql::value::serde::ser;
|
use crate::sql::value::serde::ser;
|
||||||
use crate::sql::Base;
|
use crate::sql::Base;
|
||||||
use crate::sql::Ident;
|
use crate::sql::Ident;
|
||||||
|
@ -12,18 +12,18 @@ use serde::ser::Serialize;
|
||||||
pub struct Serializer;
|
pub struct Serializer;
|
||||||
|
|
||||||
impl ser::Serializer for Serializer {
|
impl ser::Serializer for Serializer {
|
||||||
type Ok = RemoveTokenStatement;
|
type Ok = RemoveAccessStatement;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
type SerializeSeq = Impossible<RemoveTokenStatement, Error>;
|
type SerializeSeq = Impossible<RemoveAccessStatement, Error>;
|
||||||
type SerializeTuple = Impossible<RemoveTokenStatement, Error>;
|
type SerializeTuple = Impossible<RemoveAccessStatement, Error>;
|
||||||
type SerializeTupleStruct = Impossible<RemoveTokenStatement, Error>;
|
type SerializeTupleStruct = Impossible<RemoveAccessStatement, Error>;
|
||||||
type SerializeTupleVariant = Impossible<RemoveTokenStatement, Error>;
|
type SerializeTupleVariant = Impossible<RemoveAccessStatement, Error>;
|
||||||
type SerializeMap = Impossible<RemoveTokenStatement, Error>;
|
type SerializeMap = Impossible<RemoveAccessStatement, Error>;
|
||||||
type SerializeStruct = SerializeRemoveTokenStatement;
|
type SerializeStruct = SerializeRemoveAccessStatement;
|
||||||
type SerializeStructVariant = Impossible<RemoveTokenStatement, Error>;
|
type SerializeStructVariant = Impossible<RemoveAccessStatement, Error>;
|
||||||
|
|
||||||
const EXPECTED: &'static str = "a struct `RemoveTokenStatement`";
|
const EXPECTED: &'static str = "a struct `RemoveAccessStatement`";
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn serialize_struct(
|
fn serialize_struct(
|
||||||
|
@ -31,20 +31,20 @@ impl ser::Serializer for Serializer {
|
||||||
_name: &'static str,
|
_name: &'static str,
|
||||||
_len: usize,
|
_len: usize,
|
||||||
) -> Result<Self::SerializeStruct, Error> {
|
) -> Result<Self::SerializeStruct, Error> {
|
||||||
Ok(SerializeRemoveTokenStatement::default())
|
Ok(SerializeRemoveAccessStatement::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct SerializeRemoveTokenStatement {
|
pub struct SerializeRemoveAccessStatement {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
base: Base,
|
base: Base,
|
||||||
if_exists: bool,
|
if_exists: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl serde::ser::SerializeStruct for SerializeRemoveTokenStatement {
|
impl serde::ser::SerializeStruct for SerializeRemoveAccessStatement {
|
||||||
type Ok = RemoveTokenStatement;
|
type Ok = RemoveAccessStatement;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||||
|
@ -63,7 +63,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveTokenStatement {
|
||||||
}
|
}
|
||||||
key => {
|
key => {
|
||||||
return Err(Error::custom(format!(
|
return Err(Error::custom(format!(
|
||||||
"unexpected field `RemoveTokenStatement::{key}`"
|
"unexpected field `RemoveAccessStatement::{key}`"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveTokenStatement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end(self) -> Result<Self::Ok, Error> {
|
fn end(self) -> Result<Self::Ok, Error> {
|
||||||
Ok(RemoveTokenStatement {
|
Ok(RemoveAccessStatement {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
base: self.base,
|
base: self.base,
|
||||||
if_exists: self.if_exists,
|
if_exists: self.if_exists,
|
||||||
|
@ -85,8 +85,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default() {
|
fn default() {
|
||||||
let stmt = RemoveTokenStatement::default();
|
let stmt = RemoveAccessStatement::default();
|
||||||
let value: RemoveTokenStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
let value: RemoveAccessStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||||
assert_eq!(value, stmt);
|
assert_eq!(value, stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod access;
|
||||||
mod analyzer;
|
mod analyzer;
|
||||||
mod database;
|
mod database;
|
||||||
mod event;
|
mod event;
|
||||||
|
@ -6,9 +7,7 @@ mod function;
|
||||||
mod index;
|
mod index;
|
||||||
mod namespace;
|
mod namespace;
|
||||||
mod param;
|
mod param;
|
||||||
mod scope;
|
|
||||||
mod table;
|
mod table;
|
||||||
mod token;
|
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
|
@ -59,8 +58,7 @@ impl ser::Serializer for Serializer {
|
||||||
"Analyzer" => {
|
"Analyzer" => {
|
||||||
Ok(RemoveStatement::Analyzer(value.serialize(analyzer::Serializer.wrap())?))
|
Ok(RemoveStatement::Analyzer(value.serialize(analyzer::Serializer.wrap())?))
|
||||||
}
|
}
|
||||||
"Token" => Ok(RemoveStatement::Token(value.serialize(token::Serializer.wrap())?)),
|
"Access" => Ok(RemoveStatement::Access(value.serialize(access::Serializer.wrap())?)),
|
||||||
"Scope" => Ok(RemoveStatement::Scope(value.serialize(scope::Serializer.wrap())?)),
|
|
||||||
"Param" => Ok(RemoveStatement::Param(value.serialize(param::Serializer.wrap())?)),
|
"Param" => Ok(RemoveStatement::Param(value.serialize(param::Serializer.wrap())?)),
|
||||||
"Table" => Ok(RemoveStatement::Table(value.serialize(table::Serializer.wrap())?)),
|
"Table" => Ok(RemoveStatement::Table(value.serialize(table::Serializer.wrap())?)),
|
||||||
"Event" => Ok(RemoveStatement::Event(value.serialize(event::Serializer.wrap())?)),
|
"Event" => Ok(RemoveStatement::Event(value.serialize(event::Serializer.wrap())?)),
|
||||||
|
@ -108,15 +106,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn token() {
|
fn access() {
|
||||||
let stmt = RemoveStatement::Token(Default::default());
|
let stmt = RemoveStatement::Access(Default::default());
|
||||||
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
|
||||||
assert_eq!(stmt, serialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn scope() {
|
|
||||||
let stmt = RemoveStatement::Scope(Default::default());
|
|
||||||
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
||||||
assert_eq!(stmt, serialized);
|
assert_eq!(stmt, serialized);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
use crate::err::Error;
|
|
||||||
use crate::sql::statements::RemoveScopeStatement;
|
|
||||||
use crate::sql::value::serde::ser;
|
|
||||||
use crate::sql::Ident;
|
|
||||||
use ser::Serializer as _;
|
|
||||||
use serde::ser::Error as _;
|
|
||||||
use serde::ser::Impossible;
|
|
||||||
use serde::ser::Serialize;
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Serializer;
|
|
||||||
|
|
||||||
impl ser::Serializer for Serializer {
|
|
||||||
type Ok = RemoveScopeStatement;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
type SerializeSeq = Impossible<RemoveScopeStatement, Error>;
|
|
||||||
type SerializeTuple = Impossible<RemoveScopeStatement, Error>;
|
|
||||||
type SerializeTupleStruct = Impossible<RemoveScopeStatement, Error>;
|
|
||||||
type SerializeTupleVariant = Impossible<RemoveScopeStatement, Error>;
|
|
||||||
type SerializeMap = Impossible<RemoveScopeStatement, Error>;
|
|
||||||
type SerializeStruct = SerializeRemoveScopeStatement;
|
|
||||||
type SerializeStructVariant = Impossible<RemoveScopeStatement, Error>;
|
|
||||||
|
|
||||||
const EXPECTED: &'static str = "a struct `RemoveScopeStatement`";
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn serialize_struct(
|
|
||||||
self,
|
|
||||||
_name: &'static str,
|
|
||||||
_len: usize,
|
|
||||||
) -> Result<Self::SerializeStruct, Error> {
|
|
||||||
Ok(SerializeRemoveScopeStatement::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct SerializeRemoveScopeStatement {
|
|
||||||
name: Ident,
|
|
||||||
if_exists: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl serde::ser::SerializeStruct for SerializeRemoveScopeStatement {
|
|
||||||
type Ok = RemoveScopeStatement;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
T: ?Sized + Serialize,
|
|
||||||
{
|
|
||||||
match key {
|
|
||||||
"name" => {
|
|
||||||
self.name = Ident(value.serialize(ser::string::Serializer.wrap())?);
|
|
||||||
}
|
|
||||||
"if_exists" => {
|
|
||||||
self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?;
|
|
||||||
}
|
|
||||||
key => {
|
|
||||||
return Err(Error::custom(format!(
|
|
||||||
"unexpected field `RemoveScopeStatement::{key}`"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end(self) -> Result<Self::Ok, Error> {
|
|
||||||
Ok(RemoveScopeStatement {
|
|
||||||
name: self.name,
|
|
||||||
if_exists: self.if_exists,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn default() {
|
|
||||||
let stmt = RemoveScopeStatement::default();
|
|
||||||
let value: RemoveScopeStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
|
||||||
assert_eq!(value, stmt);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -56,7 +56,9 @@ pub fn could_be_reserved(s: &str) -> bool {
|
||||||
/// A map for mapping keyword strings to a tokenkind,
|
/// A map for mapping keyword strings to a tokenkind,
|
||||||
pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map! {
|
pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map! {
|
||||||
// Keywords
|
// Keywords
|
||||||
|
UniCase::ascii("ACCESS") => TokenKind::Keyword(Keyword::Access),
|
||||||
UniCase::ascii("AFTER") => TokenKind::Keyword(Keyword::After),
|
UniCase::ascii("AFTER") => TokenKind::Keyword(Keyword::After),
|
||||||
|
UniCase::ascii("ALGORITHM") => TokenKind::Keyword(Keyword::Algorithm),
|
||||||
UniCase::ascii("ALL") => TokenKind::Keyword(Keyword::All),
|
UniCase::ascii("ALL") => TokenKind::Keyword(Keyword::All),
|
||||||
UniCase::ascii("ANALYZE") => TokenKind::Keyword(Keyword::Analyze),
|
UniCase::ascii("ANALYZE") => TokenKind::Keyword(Keyword::Analyze),
|
||||||
UniCase::ascii("ANALYZER") => TokenKind::Keyword(Keyword::Analyzer),
|
UniCase::ascii("ANALYZER") => TokenKind::Keyword(Keyword::Analyzer),
|
||||||
|
@ -132,6 +134,9 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
||||||
UniCase::ascii("INTO") => TokenKind::Keyword(Keyword::Into),
|
UniCase::ascii("INTO") => TokenKind::Keyword(Keyword::Into),
|
||||||
UniCase::ascii("IF") => TokenKind::Keyword(Keyword::If),
|
UniCase::ascii("IF") => TokenKind::Keyword(Keyword::If),
|
||||||
UniCase::ascii("IS") => TokenKind::Keyword(Keyword::Is),
|
UniCase::ascii("IS") => TokenKind::Keyword(Keyword::Is),
|
||||||
|
UniCase::ascii("ISSUER") => TokenKind::Keyword(Keyword::Issuer),
|
||||||
|
UniCase::ascii("JWT") => TokenKind::Keyword(Keyword::Jwt),
|
||||||
|
UniCase::ascii("JWKS") => TokenKind::Keyword(Keyword::Jwks),
|
||||||
UniCase::ascii("KEY") => TokenKind::Keyword(Keyword::Key),
|
UniCase::ascii("KEY") => TokenKind::Keyword(Keyword::Key),
|
||||||
UniCase::ascii("KEEP_PRUNED_CONNECTIONS") => TokenKind::Keyword(Keyword::KeepPrunedConnections),
|
UniCase::ascii("KEEP_PRUNED_CONNECTIONS") => TokenKind::Keyword(Keyword::KeepPrunedConnections),
|
||||||
UniCase::ascii("KILL") => TokenKind::Keyword(Keyword::Kill),
|
UniCase::ascii("KILL") => TokenKind::Keyword(Keyword::Kill),
|
||||||
|
@ -214,6 +219,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
||||||
UniCase::ascii("UNSET") => TokenKind::Keyword(Keyword::Unset),
|
UniCase::ascii("UNSET") => TokenKind::Keyword(Keyword::Unset),
|
||||||
UniCase::ascii("UPDATE") => TokenKind::Keyword(Keyword::Update),
|
UniCase::ascii("UPDATE") => TokenKind::Keyword(Keyword::Update),
|
||||||
UniCase::ascii("UPPERCASE") => TokenKind::Keyword(Keyword::Uppercase),
|
UniCase::ascii("UPPERCASE") => TokenKind::Keyword(Keyword::Uppercase),
|
||||||
|
UniCase::ascii("URL") => TokenKind::Keyword(Keyword::Url),
|
||||||
UniCase::ascii("USE") => TokenKind::Keyword(Keyword::Use),
|
UniCase::ascii("USE") => TokenKind::Keyword(Keyword::Use),
|
||||||
UniCase::ascii("USER") => TokenKind::Keyword(Keyword::User),
|
UniCase::ascii("USER") => TokenKind::Keyword(Keyword::User),
|
||||||
UniCase::ascii("VALUE") => TokenKind::Keyword(Keyword::Value),
|
UniCase::ascii("VALUE") => TokenKind::Keyword(Keyword::Value),
|
||||||
|
@ -338,7 +344,6 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
||||||
UniCase::ascii("RS256") => TokenKind::Algorithm(Algorithm::Rs256),
|
UniCase::ascii("RS256") => TokenKind::Algorithm(Algorithm::Rs256),
|
||||||
UniCase::ascii("RS384") => TokenKind::Algorithm(Algorithm::Rs384),
|
UniCase::ascii("RS384") => TokenKind::Algorithm(Algorithm::Rs384),
|
||||||
UniCase::ascii("RS512") => TokenKind::Algorithm(Algorithm::Rs512),
|
UniCase::ascii("RS512") => TokenKind::Algorithm(Algorithm::Rs512),
|
||||||
UniCase::ascii("JWKS") => jwks_token_kind(), // Necessary because `phf_map!` doesn't support `cfg` attributes
|
|
||||||
|
|
||||||
// Distance
|
// Distance
|
||||||
UniCase::ascii("CHEBYSHEV") => TokenKind::Distance(DistanceKind::Chebyshev),
|
UniCase::ascii("CHEBYSHEV") => TokenKind::Distance(DistanceKind::Chebyshev),
|
||||||
|
@ -360,11 +365,3 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
||||||
// Change Feed keywords
|
// Change Feed keywords
|
||||||
UniCase::ascii("ORIGINAL") => TokenKind::ChangeFeedInclude(ChangeFeedInclude::Original),
|
UniCase::ascii("ORIGINAL") => TokenKind::ChangeFeedInclude(ChangeFeedInclude::Original),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fn jwks_token_kind() -> TokenKind {
|
|
||||||
#[cfg(feature = "jwks")]
|
|
||||||
let token = TokenKind::Algorithm(Algorithm::Jwks);
|
|
||||||
#[cfg(not(feature = "jwks"))]
|
|
||||||
let token = TokenKind::Identifier;
|
|
||||||
token
|
|
||||||
}
|
|
||||||
|
|
|
@ -226,8 +226,8 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
|
||||||
UniCase::ascii("session::ip") => PathKind::Function,
|
UniCase::ascii("session::ip") => PathKind::Function,
|
||||||
UniCase::ascii("session::ns") => PathKind::Function,
|
UniCase::ascii("session::ns") => PathKind::Function,
|
||||||
UniCase::ascii("session::origin") => PathKind::Function,
|
UniCase::ascii("session::origin") => PathKind::Function,
|
||||||
UniCase::ascii("session::sc") => PathKind::Function,
|
UniCase::ascii("session::ac") => PathKind::Function,
|
||||||
UniCase::ascii("session::sd") => PathKind::Function,
|
UniCase::ascii("session::rd") => PathKind::Function,
|
||||||
UniCase::ascii("session::token") => PathKind::Function,
|
UniCase::ascii("session::token") => PathKind::Function,
|
||||||
//
|
//
|
||||||
UniCase::ascii("string::concat") => PathKind::Function,
|
UniCase::ascii("string::concat") => PathKind::Function,
|
||||||
|
|
|
@ -1,26 +1,30 @@
|
||||||
use reblessive::Stk;
|
use reblessive::Stk;
|
||||||
|
|
||||||
|
use crate::sql::access_type::JwtAccessVerify;
|
||||||
use crate::sql::index::HnswParams;
|
use crate::sql::index::HnswParams;
|
||||||
use crate::{
|
use crate::{
|
||||||
sql::{
|
sql::{
|
||||||
|
access_type,
|
||||||
|
base::Base,
|
||||||
filter::Filter,
|
filter::Filter,
|
||||||
index::{Distance, VectorType},
|
index::{Distance, VectorType},
|
||||||
statements::{
|
statements::{
|
||||||
DefineAnalyzerStatement, DefineDatabaseStatement, DefineEventStatement,
|
DefineAccessStatement, DefineAnalyzerStatement, DefineDatabaseStatement,
|
||||||
DefineFieldStatement, DefineFunctionStatement, DefineIndexStatement,
|
DefineEventStatement, DefineFieldStatement, DefineFunctionStatement,
|
||||||
DefineNamespaceStatement, DefineParamStatement, DefineScopeStatement, DefineStatement,
|
DefineIndexStatement, DefineNamespaceStatement, DefineParamStatement, DefineStatement,
|
||||||
DefineTableStatement, DefineTokenStatement, DefineUserStatement,
|
DefineTableStatement, DefineUserStatement,
|
||||||
},
|
},
|
||||||
table_type,
|
table_type,
|
||||||
tokenizer::Tokenizer,
|
tokenizer::Tokenizer,
|
||||||
Ident, Idioms, Index, Kind, Param, Permissions, Scoring, Strand, TableType, Values,
|
AccessType, Ident, Idioms, Index, Kind, Param, Permissions, Scoring, Strand, TableType,
|
||||||
|
Values,
|
||||||
},
|
},
|
||||||
syn::{
|
syn::{
|
||||||
parser::{
|
parser::{
|
||||||
mac::{expected, unexpected},
|
mac::{expected, unexpected},
|
||||||
ParseResult, Parser,
|
ParseResult, Parser,
|
||||||
},
|
},
|
||||||
token::{t, TokenKind},
|
token::{t, Keyword, TokenKind},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,8 +35,8 @@ impl Parser<'_> {
|
||||||
t!("DATABASE") => self.parse_define_database().map(DefineStatement::Database),
|
t!("DATABASE") => self.parse_define_database().map(DefineStatement::Database),
|
||||||
t!("FUNCTION") => self.parse_define_function(ctx).await.map(DefineStatement::Function),
|
t!("FUNCTION") => self.parse_define_function(ctx).await.map(DefineStatement::Function),
|
||||||
t!("USER") => self.parse_define_user().map(DefineStatement::User),
|
t!("USER") => self.parse_define_user().map(DefineStatement::User),
|
||||||
t!("TOKEN") => self.parse_define_token().map(DefineStatement::Token),
|
t!("TOKEN") => self.parse_define_token().map(DefineStatement::Access),
|
||||||
t!("SCOPE") => self.parse_define_scope(ctx).await.map(DefineStatement::Scope),
|
t!("SCOPE") => self.parse_define_scope(ctx).await.map(DefineStatement::Access),
|
||||||
t!("PARAM") => self.parse_define_param(ctx).await.map(DefineStatement::Param),
|
t!("PARAM") => self.parse_define_param(ctx).await.map(DefineStatement::Param),
|
||||||
t!("TABLE") => self.parse_define_table(ctx).await.map(DefineStatement::Table),
|
t!("TABLE") => self.parse_define_table(ctx).await.map(DefineStatement::Table),
|
||||||
t!("EVENT") => {
|
t!("EVENT") => {
|
||||||
|
@ -43,6 +47,7 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
t!("INDEX") => self.parse_define_index().map(DefineStatement::Index),
|
t!("INDEX") => self.parse_define_index().map(DefineStatement::Index),
|
||||||
t!("ANALYZER") => self.parse_define_analyzer().map(DefineStatement::Analyzer),
|
t!("ANALYZER") => self.parse_define_analyzer().map(DefineStatement::Analyzer),
|
||||||
|
t!("ACCESS") => self.parse_define_access(ctx).await.map(DefineStatement::Access),
|
||||||
x => unexpected!(self, x, "a define statement keyword"),
|
x => unexpected!(self, x, "a define statement keyword"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +216,10 @@ impl Parser<'_> {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_define_token(&mut self) -> ParseResult<DefineTokenStatement> {
|
pub async fn parse_define_access(
|
||||||
|
&mut self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
) -> ParseResult<DefineAccessStatement> {
|
||||||
let if_not_exists = if self.eat(t!("IF")) {
|
let if_not_exists = if self.eat(t!("IF")) {
|
||||||
expected!(self, t!("NOT"));
|
expected!(self, t!("NOT"));
|
||||||
expected!(self, t!("EXISTS"));
|
expected!(self, t!("EXISTS"));
|
||||||
|
@ -221,9 +229,10 @@ impl Parser<'_> {
|
||||||
};
|
};
|
||||||
let name = self.next_token_value()?;
|
let name = self.next_token_value()?;
|
||||||
expected!(self, t!("ON"));
|
expected!(self, t!("ON"));
|
||||||
let base = self.parse_base(true)?;
|
// TODO: Parse base should no longer take an argument.
|
||||||
|
let base = self.parse_base(false)?;
|
||||||
|
|
||||||
let mut res = DefineTokenStatement {
|
let mut res = DefineAccessStatement {
|
||||||
name,
|
name,
|
||||||
base,
|
base,
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
|
@ -236,17 +245,49 @@ impl Parser<'_> {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
res.comment = Some(self.next_token_value()?);
|
res.comment = Some(self.next_token_value()?);
|
||||||
}
|
}
|
||||||
t!("VALUE") => {
|
|
||||||
self.pop_peek();
|
|
||||||
res.code = self.next_token_value::<Strand>()?.0;
|
|
||||||
}
|
|
||||||
t!("TYPE") => {
|
t!("TYPE") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
match self.next().kind {
|
match self.peek_kind() {
|
||||||
TokenKind::Algorithm(x) => {
|
t!("JWT") => {
|
||||||
res.kind = x;
|
self.pop_peek();
|
||||||
|
res.kind = AccessType::Jwt(self.parse_jwt(None)?);
|
||||||
}
|
}
|
||||||
x => unexpected!(self, x, "a token algorithm"),
|
t!("RECORD") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let mut ac = access_type::RecordAccess {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
loop {
|
||||||
|
match self.peek_kind() {
|
||||||
|
t!("DURATION") => {
|
||||||
|
self.pop_peek();
|
||||||
|
ac.duration = Some(self.next_token_value()?);
|
||||||
|
// By default, token duration matches session duration
|
||||||
|
// The token duration can be modified in the WITH JWT clause
|
||||||
|
if let Some(ref mut iss) = ac.jwt.issue {
|
||||||
|
iss.duration = ac.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t!("SIGNUP") => {
|
||||||
|
self.pop_peek();
|
||||||
|
ac.signup =
|
||||||
|
Some(stk.run(|stk| self.parse_value(stk)).await?);
|
||||||
|
}
|
||||||
|
t!("SIGNIN") => {
|
||||||
|
self.pop_peek();
|
||||||
|
ac.signin =
|
||||||
|
Some(stk.run(|stk| self.parse_value(stk)).await?);
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.eat(t!("WITH")) {
|
||||||
|
expected!(self, t!("JWT"));
|
||||||
|
ac.jwt = self.parse_jwt(Some(AccessType::Record(ac.clone())))?;
|
||||||
|
}
|
||||||
|
res.kind = AccessType::Record(ac);
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
|
@ -256,7 +297,8 @@ impl Parser<'_> {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn parse_define_scope(&mut self, stk: &mut Stk) -> ParseResult<DefineScopeStatement> {
|
// TODO(gguillemas): Deprecated in 2.0.0. Drop this in 3.0.0 in favor of DEFINE ACCESS
|
||||||
|
pub fn parse_define_token(&mut self) -> ParseResult<DefineAccessStatement> {
|
||||||
let if_not_exists = if self.eat(t!("IF")) {
|
let if_not_exists = if self.eat(t!("IF")) {
|
||||||
expected!(self, t!("NOT"));
|
expected!(self, t!("NOT"));
|
||||||
expected!(self, t!("EXISTS"));
|
expected!(self, t!("EXISTS"));
|
||||||
|
@ -265,13 +307,130 @@ impl Parser<'_> {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
let name = self.next_token_value()?;
|
let name = self.next_token_value()?;
|
||||||
let mut res = DefineScopeStatement {
|
expected!(self, t!("ON"));
|
||||||
|
let base = self.parse_base(true)?;
|
||||||
|
|
||||||
|
let mut res = DefineAccessStatement {
|
||||||
name,
|
name,
|
||||||
code: DefineScopeStatement::random_code(),
|
base: base.clone(),
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match base {
|
||||||
|
// DEFINE TOKEN ON SCOPE is now record access with JWT
|
||||||
|
Base::Sc(_) => {
|
||||||
|
res.base = Base::Db;
|
||||||
|
let mut ac = access_type::RecordAccess {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
ac.jwt.issue = None;
|
||||||
|
loop {
|
||||||
|
match self.peek_kind() {
|
||||||
|
t!("COMMENT") => {
|
||||||
|
self.pop_peek();
|
||||||
|
res.comment = Some(self.next_token_value()?);
|
||||||
|
}
|
||||||
|
// For backward compatibility, value is always expected after type
|
||||||
|
// This matches the display format of the legacy statement
|
||||||
|
t!("TYPE") => {
|
||||||
|
self.pop_peek();
|
||||||
|
match self.next().kind {
|
||||||
|
TokenKind::Algorithm(alg) => {
|
||||||
|
expected!(self, t!("VALUE"));
|
||||||
|
ac.jwt.verify = access_type::JwtAccessVerify::Key(
|
||||||
|
access_type::JwtAccessVerifyKey {
|
||||||
|
alg,
|
||||||
|
key: self.next_token_value::<Strand>()?.0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
TokenKind::Keyword(Keyword::Jwks) => {
|
||||||
|
expected!(self, t!("VALUE"));
|
||||||
|
ac.jwt.verify = access_type::JwtAccessVerify::Jwks(
|
||||||
|
access_type::JwtAccessVerifyJwks {
|
||||||
|
url: self.next_token_value::<Strand>()?.0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
x => unexpected!(self, x, "a token algorithm or 'JWKS'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.kind = AccessType::Record(ac);
|
||||||
|
}
|
||||||
|
// DEFINE TOKEN anywhere else is now JWT access
|
||||||
|
_ => {
|
||||||
|
let mut ac = access_type::JwtAccess {
|
||||||
|
issue: None,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
loop {
|
||||||
|
match self.peek_kind() {
|
||||||
|
t!("COMMENT") => {
|
||||||
|
self.pop_peek();
|
||||||
|
res.comment = Some(self.next_token_value()?);
|
||||||
|
}
|
||||||
|
// For backward compatibility, value is always expected after type
|
||||||
|
// This matches the display format of the legacy statement
|
||||||
|
t!("TYPE") => {
|
||||||
|
self.pop_peek();
|
||||||
|
match self.next().kind {
|
||||||
|
TokenKind::Algorithm(alg) => {
|
||||||
|
expected!(self, t!("VALUE"));
|
||||||
|
ac.verify = access_type::JwtAccessVerify::Key(
|
||||||
|
access_type::JwtAccessVerifyKey {
|
||||||
|
alg,
|
||||||
|
key: self.next_token_value::<Strand>()?.0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
TokenKind::Keyword(Keyword::Jwks) => {
|
||||||
|
expected!(self, t!("VALUE"));
|
||||||
|
ac.verify = access_type::JwtAccessVerify::Jwks(
|
||||||
|
access_type::JwtAccessVerifyJwks {
|
||||||
|
url: self.next_token_value::<Strand>()?.0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
x => unexpected!(self, x, "a token algorithm or 'JWKS'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.kind = AccessType::Jwt(ac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gguillemas): Deprecated in 2.0.0. Drop this in 3.0.0 in favor of DEFINE ACCESS
|
||||||
|
pub async fn parse_define_scope(
|
||||||
|
&mut self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
) -> ParseResult<DefineAccessStatement> {
|
||||||
|
let if_not_exists = if self.eat(t!("IF")) {
|
||||||
|
expected!(self, t!("NOT"));
|
||||||
|
expected!(self, t!("EXISTS"));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let name = self.next_token_value()?;
|
||||||
|
let mut res = DefineAccessStatement {
|
||||||
|
name,
|
||||||
|
base: Base::Db,
|
||||||
|
if_not_exists,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut ac = access_type::RecordAccess {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match self.peek_kind() {
|
match self.peek_kind() {
|
||||||
t!("COMMENT") => {
|
t!("COMMENT") => {
|
||||||
|
@ -280,20 +439,26 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
t!("SESSION") => {
|
t!("SESSION") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
res.session = Some(self.next_token_value()?);
|
ac.duration = Some(self.next_token_value()?);
|
||||||
|
// By default, token duration matches session duration.
|
||||||
|
if let Some(ref mut iss) = ac.jwt.issue {
|
||||||
|
iss.duration = ac.duration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
t!("SIGNUP") => {
|
t!("SIGNUP") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
res.signup = Some(stk.run(|stk| self.parse_value(stk)).await?);
|
ac.signup = Some(stk.run(|stk| self.parse_value(stk)).await?);
|
||||||
}
|
}
|
||||||
t!("SIGNIN") => {
|
t!("SIGNIN") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
res.signin = Some(stk.run(|stk| self.parse_value(stk)).await?);
|
ac.signin = Some(stk.run(|stk| self.parse_value(stk)).await?);
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.kind = AccessType::Record(ac);
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -914,4 +1079,110 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
Ok(Kind::Record(names))
|
Ok(Kind::Record(names))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_jwt(&mut self, ac: Option<AccessType>) -> ParseResult<access_type::JwtAccess> {
|
||||||
|
let mut res = access_type::JwtAccess {
|
||||||
|
// By default, a JWT access method is only used to verify.
|
||||||
|
issue: None,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut iss = access_type::JwtAccessIssue {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// If an access method was passed, inherit any relevant defaults.
|
||||||
|
// This will become a match statement whenever more access methods are available.
|
||||||
|
if let Some(AccessType::Record(ac)) = ac {
|
||||||
|
// By default, token duration is inherited from session duration in record access.
|
||||||
|
iss.duration = ac.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.peek_kind() {
|
||||||
|
t!("ALGORITHM") => {
|
||||||
|
self.pop_peek();
|
||||||
|
match self.next().kind {
|
||||||
|
TokenKind::Algorithm(alg) => match self.next().kind {
|
||||||
|
t!("KEY") => {
|
||||||
|
let key = self.next_token_value::<Strand>()?.0;
|
||||||
|
res.verify = access_type::JwtAccessVerify::Key(
|
||||||
|
access_type::JwtAccessVerifyKey {
|
||||||
|
alg,
|
||||||
|
key: key.to_owned(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Currently, issuer and verifier must use the same algorithm.
|
||||||
|
iss.alg = alg;
|
||||||
|
|
||||||
|
// If the algorithm is symmetric, the issuer and verifier keys are the same.
|
||||||
|
// For asymmetric algorithms, the key needs to be explicitly defined.
|
||||||
|
if alg.is_symmetric() {
|
||||||
|
iss.key = key;
|
||||||
|
// Since all the issuer data is known, it can already be assigned.
|
||||||
|
// Cloning allows updating the original with any explicit issuer data.
|
||||||
|
res.issue = Some(iss.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x => unexpected!(self, x, "a key"),
|
||||||
|
},
|
||||||
|
x => unexpected!(self, x, "a valid algorithm"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t!("URL") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let url = self.next_token_value::<Strand>()?.0;
|
||||||
|
res.verify = access_type::JwtAccessVerify::Jwks(access_type::JwtAccessVerifyJwks {
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
x => unexpected!(self, x, "`ALGORITHM`, or `URL`"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.eat(t!("WITH")) {
|
||||||
|
expected!(self, t!("ISSUER"));
|
||||||
|
loop {
|
||||||
|
match self.peek_kind() {
|
||||||
|
t!("ALGORITHM") => {
|
||||||
|
self.pop_peek();
|
||||||
|
match self.next().kind {
|
||||||
|
TokenKind::Algorithm(alg) => {
|
||||||
|
// If an algorithm is already defined, a different value is not expected.
|
||||||
|
if let JwtAccessVerify::Key(ref ver) = res.verify {
|
||||||
|
if alg != ver.alg {
|
||||||
|
unexpected!(
|
||||||
|
self,
|
||||||
|
t!("ALGORITHM"),
|
||||||
|
"a compatible algorithm or no algorithm"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iss.alg = alg;
|
||||||
|
}
|
||||||
|
x => unexpected!(self, x, "a valid algorithm"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t!("KEY") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let key = self.next_token_value::<Strand>()?.0;
|
||||||
|
// If the algorithm is symmetric and a key is already defined, a different key is not expected.
|
||||||
|
if let JwtAccessVerify::Key(ref ver) = res.verify {
|
||||||
|
if ver.alg.is_symmetric() && key != ver.key {
|
||||||
|
unexpected!(self, t!("KEY"), "a symmetric key or no key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iss.key = key;
|
||||||
|
}
|
||||||
|
t!("DURATION") => {
|
||||||
|
self.pop_peek();
|
||||||
|
iss.duration = Some(self.next_token_value()?);
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.issue = Some(iss);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -438,10 +438,6 @@ impl Parser<'_> {
|
||||||
t!("ROOT") => InfoStatement::Root(false),
|
t!("ROOT") => InfoStatement::Root(false),
|
||||||
t!("NAMESPACE") => InfoStatement::Ns(false),
|
t!("NAMESPACE") => InfoStatement::Ns(false),
|
||||||
t!("DATABASE") => InfoStatement::Db(false),
|
t!("DATABASE") => InfoStatement::Db(false),
|
||||||
t!("SCOPE") => {
|
|
||||||
let ident = self.next_token_value()?;
|
|
||||||
InfoStatement::Sc(ident, false)
|
|
||||||
}
|
|
||||||
t!("TABLE") => {
|
t!("TABLE") => {
|
||||||
let ident = self.next_token_value()?;
|
let ident = self.next_token_value()?;
|
||||||
InfoStatement::Tb(ident, false)
|
InfoStatement::Tb(ident, false)
|
||||||
|
|
|
@ -307,9 +307,10 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(gguillemas): Deprecated in 2.0.0. Kept for backward compatibility. Drop it in 3.0.0.
|
||||||
/// Parses a base
|
/// Parses a base
|
||||||
///
|
///
|
||||||
/// So either `NAMESPACE`, ~DATABASE`, `ROOT`, or `SCOPE` if `scope_allowed` is true.
|
/// So either `NAMESPACE`, `DATABASE`, `ROOT`, or `SCOPE` if `scope_allowed` is true.
|
||||||
///
|
///
|
||||||
/// # Parser state
|
/// # Parser state
|
||||||
/// Expects the next keyword to be a base.
|
/// Expects the next keyword to be a base.
|
||||||
|
@ -327,9 +328,9 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
x => {
|
x => {
|
||||||
if scope_allowed {
|
if scope_allowed {
|
||||||
unexpected!(self, x, "'NAMEPSPACE', 'DATABASE', 'ROOT', 'SCOPE' or 'KV'")
|
unexpected!(self, x, "'NAMEPSPACE', 'DATABASE', 'ROOT' or 'SCOPE'")
|
||||||
} else {
|
} else {
|
||||||
unexpected!(self, x, "'NAMEPSPACE', 'DATABASE', 'ROOT', or 'KV'")
|
unexpected!(self, x, "'NAMEPSPACE', 'DATABASE' or 'ROOT'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
sql::{
|
sql::{
|
||||||
statements::{
|
statements::{
|
||||||
remove::RemoveAnalyzerStatement, RemoveDatabaseStatement, RemoveEventStatement,
|
remove::RemoveAnalyzerStatement, RemoveAccessStatement, RemoveDatabaseStatement,
|
||||||
RemoveFieldStatement, RemoveFunctionStatement, RemoveIndexStatement,
|
RemoveEventStatement, RemoveFieldStatement, RemoveFunctionStatement,
|
||||||
RemoveNamespaceStatement, RemoveParamStatement, RemoveScopeStatement, RemoveStatement,
|
RemoveIndexStatement, RemoveNamespaceStatement, RemoveParamStatement, RemoveStatement,
|
||||||
RemoveUserStatement,
|
RemoveUserStatement,
|
||||||
},
|
},
|
||||||
Param,
|
Param,
|
||||||
|
@ -66,7 +66,7 @@ impl Parser<'_> {
|
||||||
if_exists,
|
if_exists,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
t!("TOKEN") => {
|
t!("ACCESS") => {
|
||||||
let if_exists = if self.eat(t!("IF")) {
|
let if_exists = if self.eat(t!("IF")) {
|
||||||
expected!(self, t!("EXISTS"));
|
expected!(self, t!("EXISTS"));
|
||||||
true
|
true
|
||||||
|
@ -75,28 +75,14 @@ impl Parser<'_> {
|
||||||
};
|
};
|
||||||
let name = self.next_token_value()?;
|
let name = self.next_token_value()?;
|
||||||
expected!(self, t!("ON"));
|
expected!(self, t!("ON"));
|
||||||
let base = self.parse_base(true)?;
|
let base = self.parse_base(false)?;
|
||||||
|
|
||||||
RemoveStatement::Token(crate::sql::statements::RemoveTokenStatement {
|
RemoveStatement::Access(RemoveAccessStatement {
|
||||||
name,
|
name,
|
||||||
base,
|
base,
|
||||||
if_exists,
|
if_exists,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
t!("SCOPE") => {
|
|
||||||
let if_exists = if self.eat(t!("IF")) {
|
|
||||||
expected!(self, t!("EXISTS"));
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
let name = self.next_token_value()?;
|
|
||||||
|
|
||||||
RemoveStatement::Scope(RemoveScopeStatement {
|
|
||||||
name,
|
|
||||||
if_exists,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
t!("PARAM") => {
|
t!("PARAM") => {
|
||||||
let if_exists = if self.eat(t!("IF")) {
|
let if_exists = if self.eat(t!("IF")) {
|
||||||
expected!(self, t!("EXISTS"));
|
expected!(self, t!("EXISTS"));
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
sql::{
|
sql::{
|
||||||
|
access_type::{
|
||||||
|
AccessType, JwtAccess, JwtAccessIssue, JwtAccessVerify, JwtAccessVerifyJwks,
|
||||||
|
JwtAccessVerifyKey, RecordAccess,
|
||||||
|
},
|
||||||
block::Entry,
|
block::Entry,
|
||||||
changefeed::ChangeFeed,
|
changefeed::ChangeFeed,
|
||||||
filter::Filter,
|
filter::Filter,
|
||||||
|
@ -8,15 +12,15 @@ use crate::{
|
||||||
statements::{
|
statements::{
|
||||||
analyze::AnalyzeStatement, show::ShowSince, show::ShowStatement, sleep::SleepStatement,
|
analyze::AnalyzeStatement, show::ShowSince, show::ShowStatement, sleep::SleepStatement,
|
||||||
BeginStatement, BreakStatement, CancelStatement, CommitStatement, ContinueStatement,
|
BeginStatement, BreakStatement, CancelStatement, CommitStatement, ContinueStatement,
|
||||||
CreateStatement, DefineAnalyzerStatement, DefineDatabaseStatement,
|
CreateStatement, DefineAccessStatement, DefineAnalyzerStatement,
|
||||||
DefineEventStatement, DefineFieldStatement, DefineFunctionStatement,
|
DefineDatabaseStatement, DefineEventStatement, DefineFieldStatement,
|
||||||
DefineIndexStatement, DefineNamespaceStatement, DefineParamStatement, DefineStatement,
|
DefineFunctionStatement, DefineIndexStatement, DefineNamespaceStatement,
|
||||||
DefineTableStatement, DefineTokenStatement, DeleteStatement, ForeachStatement,
|
DefineParamStatement, DefineStatement, DefineTableStatement, DeleteStatement,
|
||||||
IfelseStatement, InfoStatement, InsertStatement, KillStatement, OptionStatement,
|
ForeachStatement, IfelseStatement, InfoStatement, InsertStatement, KillStatement,
|
||||||
OutputStatement, RelateStatement, RemoveAnalyzerStatement, RemoveDatabaseStatement,
|
OptionStatement, OutputStatement, RelateStatement, RemoveAccessStatement,
|
||||||
RemoveEventStatement, RemoveFieldStatement, RemoveFunctionStatement,
|
RemoveAnalyzerStatement, RemoveDatabaseStatement, RemoveEventStatement,
|
||||||
RemoveIndexStatement, RemoveNamespaceStatement, RemoveParamStatement,
|
RemoveFieldStatement, RemoveFunctionStatement, RemoveIndexStatement,
|
||||||
RemoveScopeStatement, RemoveStatement, RemoveTableStatement, RemoveTokenStatement,
|
RemoveNamespaceStatement, RemoveParamStatement, RemoveStatement, RemoveTableStatement,
|
||||||
RemoveUserStatement, SelectStatement, SetStatement, ThrowStatement, UpdateStatement,
|
RemoveUserStatement, SelectStatement, SetStatement, ThrowStatement, UpdateStatement,
|
||||||
UseStatement,
|
UseStatement,
|
||||||
},
|
},
|
||||||
|
@ -219,26 +223,132 @@ fn parse_define_user() {
|
||||||
assert_eq!(stmt.comment, Some(Strand("*******".to_string())))
|
assert_eq!(stmt.comment, Some(Strand("*******".to_string())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(gguillemas): This test is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_define_token() {
|
fn parse_define_token() {
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE TOKEN a ON DATABASE TYPE EDDSA VALUE "foo" COMMENT "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::EdDSA,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: None,
|
||||||
|
}),
|
||||||
|
comment: Some(Strand("bar".to_string())),
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gguillemas): This test is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||||
|
#[test]
|
||||||
|
fn parse_define_token_on_scope() {
|
||||||
let res = test_parse!(
|
let res = test_parse!(
|
||||||
parse_stmt,
|
parse_stmt,
|
||||||
r#"DEFINE TOKEN a ON SCOPE b TYPE EDDSA VALUE "foo" COMMENT "bar""#
|
r#"DEFINE TOKEN a ON SCOPE b TYPE EDDSA VALUE "foo" COMMENT "bar""#
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// Manually compare since DefineAccessStatement for record access
|
||||||
|
// without explicit JWT will create a random signing key during parsing.
|
||||||
|
let Statement::Define(DefineStatement::Access(stmt)) = res else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(stmt.name, Ident("a".to_string()));
|
||||||
|
assert_eq!(stmt.base, Base::Db); // Scope base is ignored.
|
||||||
|
assert_eq!(stmt.comment, Some(Strand("bar".to_string())));
|
||||||
|
assert_eq!(stmt.if_not_exists, false);
|
||||||
|
match stmt.kind {
|
||||||
|
AccessType::Record(ac) => {
|
||||||
|
// A session duration of one hour is set by default.
|
||||||
|
assert_eq!(ac.duration, Some(Duration::from_hours(1)));
|
||||||
|
assert_eq!(ac.signup, None);
|
||||||
|
assert_eq!(ac.signin, None);
|
||||||
|
match ac.jwt.verify {
|
||||||
|
JwtAccessVerify::Key(key) => {
|
||||||
|
assert_eq!(key.alg, Algorithm::EdDSA);
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
assert_eq!(ac.jwt.issue, None);
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gguillemas): This test is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||||
|
#[test]
|
||||||
|
fn parse_define_token_jwks() {
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE TOKEN a ON DATABASE TYPE JWKS VALUE "http://example.com/.well-known/jwks.json" COMMENT "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
Statement::Define(DefineStatement::Token(DefineTokenStatement {
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
name: Ident("a".to_string()),
|
name: Ident("a".to_string()),
|
||||||
base: Base::Sc(Ident("b".to_string())),
|
base: Base::Db,
|
||||||
kind: Algorithm::EdDSA,
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
code: "foo".to_string(),
|
verify: JwtAccessVerify::Jwks(JwtAccessVerifyJwks {
|
||||||
|
url: "http://example.com/.well-known/jwks.json".to_string(),
|
||||||
|
}),
|
||||||
|
issue: None,
|
||||||
|
}),
|
||||||
comment: Some(Strand("bar".to_string())),
|
comment: Some(Strand("bar".to_string())),
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
}))
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(gguillemas): This test is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||||
|
#[test]
|
||||||
|
fn parse_define_token_jwks_on_scope() {
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE TOKEN a ON SCOPE b TYPE JWKS VALUE "http://example.com/.well-known/jwks.json" COMMENT "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Manually compare since DefineAccessStatement for record access
|
||||||
|
// without explicit JWT will create a random signing key during parsing.
|
||||||
|
let Statement::Define(DefineStatement::Access(stmt)) = res else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(stmt.name, Ident("a".to_string()));
|
||||||
|
assert_eq!(stmt.base, Base::Db); // Scope base is ignored.
|
||||||
|
assert_eq!(stmt.comment, Some(Strand("bar".to_string())));
|
||||||
|
assert_eq!(stmt.if_not_exists, false);
|
||||||
|
match stmt.kind {
|
||||||
|
AccessType::Record(ac) => {
|
||||||
|
// A session duration of one hour is set by default.
|
||||||
|
assert_eq!(ac.duration, Some(Duration::from_hours(1)));
|
||||||
|
assert_eq!(ac.signup, None);
|
||||||
|
assert_eq!(ac.signin, None);
|
||||||
|
match ac.jwt.verify {
|
||||||
|
JwtAccessVerify::Jwks(jwks) => {
|
||||||
|
assert_eq!(jwks.url, "http://example.com/.well-known/jwks.json");
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
assert_eq!(ac.jwt.issue, None);
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gguillemas): This test is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_define_scope() {
|
fn parse_define_scope() {
|
||||||
let res = test_parse!(
|
let res = test_parse!(
|
||||||
|
@ -247,16 +357,568 @@ fn parse_define_scope() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// manually compare since DefineScopeStatement creates a random code in its parser.
|
// Manually compare since DefineAccessStatement for record access
|
||||||
let Statement::Define(DefineStatement::Scope(stmt)) = res else {
|
// without explicit JWT will create a random signing key during parsing.
|
||||||
|
let Statement::Define(DefineStatement::Access(stmt)) = res else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(stmt.name, Ident("a".to_string()));
|
assert_eq!(stmt.name, Ident("a".to_string()));
|
||||||
|
assert_eq!(stmt.base, Base::Db);
|
||||||
assert_eq!(stmt.comment, Some(Strand("bar".to_string())));
|
assert_eq!(stmt.comment, Some(Strand("bar".to_string())));
|
||||||
assert_eq!(stmt.session, Some(Duration(std::time::Duration::from_secs(1))));
|
assert_eq!(stmt.if_not_exists, false);
|
||||||
assert_eq!(stmt.signup, Some(Value::Bool(true)));
|
match stmt.kind {
|
||||||
assert_eq!(stmt.signin, Some(Value::Bool(false)));
|
AccessType::Record(ac) => {
|
||||||
|
assert_eq!(ac.duration, Some(Duration(std::time::Duration::from_secs(1))));
|
||||||
|
assert_eq!(ac.signup, Some(Value::Bool(true)));
|
||||||
|
assert_eq!(ac.signin, Some(Value::Bool(false)));
|
||||||
|
match ac.jwt.verify {
|
||||||
|
JwtAccessVerify::Key(key) => {
|
||||||
|
assert_eq!(key.alg, Algorithm::Hs512);
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
match ac.jwt.issue {
|
||||||
|
Some(iss) => {
|
||||||
|
assert_eq!(iss.alg, Algorithm::Hs512);
|
||||||
|
// Token duration matches session duration by default.
|
||||||
|
assert_eq!(iss.duration, Some(Duration::from_secs(1)));
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_define_access_jwt_key() {
|
||||||
|
// With comment. Asymmetric verify only.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT ALGORITHM EDDSA KEY "foo" COMMENT "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::EdDSA,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: None,
|
||||||
|
}),
|
||||||
|
comment: Some(Strand("bar".to_string())),
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Asymmetric verify and issue.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT ALGORITHM EDDSA KEY "foo" WITH ISSUER KEY "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::EdDSA,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::EdDSA,
|
||||||
|
key: "bar".to_string(),
|
||||||
|
// Default duration.
|
||||||
|
duration: Some(Duration::from_hours(1)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Symmetric verify and implicit issue.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT ALGORITHM HS256 KEY "foo""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::Hs256,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Hs256,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
// Default duration.
|
||||||
|
duration: Some(Duration::from_hours(1)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Symmetric verify and explicit issue.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT ALGORITHM HS256 KEY "foo" WITH ISSUER DURATION 10s"#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::Hs256,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Hs256,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
duration: Some(Duration::from_secs(10)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Symmetric verify and explicit issue matching data.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT ALGORITHM HS256 KEY "foo" WITH ISSUER ALGORITHM HS256 KEY "foo" DURATION 10s"#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::Hs256,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Hs256,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
duration: Some(Duration::from_secs(10)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Symmetric verify and explicit issue non-matching data.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT ALGORITHM HS256 KEY "foo" WITH ISSUER ALGORITHM HS384 KEY "bar" DURATION 10s"#
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
res.is_err(),
|
||||||
|
"Unexpected successful parsing of non-matching verifier and issuer: {:?}",
|
||||||
|
res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Symmetric verify and explicit issue non-matching key.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT ALGORITHM HS256 KEY "foo" WITH ISSUER KEY "bar" DURATION 10s"#
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
res.is_err(),
|
||||||
|
"Unexpected successful parsing of non-matching verifier and issuer: {:?}",
|
||||||
|
res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Symmetric verify and explicit issue non-matching algorithm.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT ALGORITHM HS256 KEY "foo" WITH ISSUER ALGORITHM HS384 DURATION 10s"#
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
res.is_err(),
|
||||||
|
"Unexpected successful parsing of non-matching verifier and issuer: {:?}",
|
||||||
|
res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_define_access_jwt_jwks() {
|
||||||
|
// With comment. Verify only.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT URL "http://example.com/.well-known/jwks.json" COMMENT "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Jwks(JwtAccessVerifyJwks {
|
||||||
|
url: "http://example.com/.well-known/jwks.json".to_string(),
|
||||||
|
}),
|
||||||
|
issue: None,
|
||||||
|
}),
|
||||||
|
comment: Some(Strand("bar".to_string())),
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Verify and symmetric issuer.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT URL "http://example.com/.well-known/jwks.json" WITH ISSUER ALGORITHM HS384 KEY "foo""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Jwks(JwtAccessVerifyJwks {
|
||||||
|
url: "http://example.com/.well-known/jwks.json".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Hs384,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
// Default duration.
|
||||||
|
duration: Some(Duration::from_hours(1)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Verify and symmetric issuer with custom duration.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT URL "http://example.com/.well-known/jwks.json" WITH ISSUER ALGORITHM HS384 KEY "foo" DURATION 10s"#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Jwks(JwtAccessVerifyJwks {
|
||||||
|
url: "http://example.com/.well-known/jwks.json".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Hs384,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
duration: Some(Duration::from_secs(10)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Verify and asymmetric issuer.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT URL "http://example.com/.well-known/jwks.json" WITH ISSUER ALGORITHM PS256 KEY "foo""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Jwks(JwtAccessVerifyJwks {
|
||||||
|
url: "http://example.com/.well-known/jwks.json".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Ps256,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
// Default duration.
|
||||||
|
duration: Some(Duration::from_hours(1)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Verify and asymmetric issuer with custom duration.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE JWT URL "http://example.com/.well-known/jwks.json" WITH ISSUER ALGORITHM PS256 KEY "foo" DURATION 10s"#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Jwks(JwtAccessVerifyJwks {
|
||||||
|
url: "http://example.com/.well-known/jwks.json".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Ps256,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
duration: Some(Duration::from_secs(10)),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_define_access_record() {
|
||||||
|
// With comment. Nothing is explicitly defined.
|
||||||
|
{
|
||||||
|
let res =
|
||||||
|
test_parse!(parse_stmt, r#"DEFINE ACCESS a ON DB TYPE RECORD COMMENT "bar""#).unwrap();
|
||||||
|
|
||||||
|
// Manually compare since DefineAccessStatement for record access
|
||||||
|
// without explicit JWT will create a random signing key during parsing.
|
||||||
|
let Statement::Define(DefineStatement::Access(stmt)) = res else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(stmt.name, Ident("a".to_string()));
|
||||||
|
assert_eq!(stmt.base, Base::Db);
|
||||||
|
assert_eq!(stmt.comment, Some(Strand("bar".to_string())));
|
||||||
|
assert_eq!(stmt.if_not_exists, false);
|
||||||
|
match stmt.kind {
|
||||||
|
AccessType::Record(ac) => {
|
||||||
|
// A session duration of one hour is set by default.
|
||||||
|
assert_eq!(ac.duration, Some(Duration::from_hours(1)));
|
||||||
|
assert_eq!(ac.signup, None);
|
||||||
|
assert_eq!(ac.signin, None);
|
||||||
|
match ac.jwt.verify {
|
||||||
|
JwtAccessVerify::Key(key) => {
|
||||||
|
assert_eq!(key.alg, Algorithm::Hs512);
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
match ac.jwt.issue {
|
||||||
|
Some(iss) => {
|
||||||
|
assert_eq!(iss.alg, Algorithm::Hs512);
|
||||||
|
// A token duration of one hour is set by default.
|
||||||
|
assert_eq!(iss.duration, Some(Duration::from_hours(1)));
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Duration and signing queries are explicitly defined.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DB TYPE RECORD DURATION 10s SIGNUP true SIGNIN false"#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Manually compare since DefineAccessStatement for record access
|
||||||
|
// without explicit JWT will create a random signing key during parsing.
|
||||||
|
let Statement::Define(DefineStatement::Access(stmt)) = res else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(stmt.name, Ident("a".to_string()));
|
||||||
|
assert_eq!(stmt.base, Base::Db);
|
||||||
|
assert_eq!(stmt.comment, None);
|
||||||
|
assert_eq!(stmt.if_not_exists, false);
|
||||||
|
match stmt.kind {
|
||||||
|
AccessType::Record(ac) => {
|
||||||
|
assert_eq!(ac.duration, Some(Duration(std::time::Duration::from_secs(10))));
|
||||||
|
assert_eq!(ac.signup, Some(Value::Bool(true)));
|
||||||
|
assert_eq!(ac.signin, Some(Value::Bool(false)));
|
||||||
|
match ac.jwt.verify {
|
||||||
|
JwtAccessVerify::Key(key) => {
|
||||||
|
assert_eq!(key.alg, Algorithm::Hs512);
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
match ac.jwt.issue {
|
||||||
|
Some(iss) => {
|
||||||
|
assert_eq!(iss.alg, Algorithm::Hs512);
|
||||||
|
// Token duration matches session duration by default.
|
||||||
|
assert_eq!(iss.duration, Some(Duration::from_secs(10)));
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Verification with JWT is explicitly defined only with symmetric key.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DB TYPE RECORD DURATION 10s WITH JWT ALGORITHM HS384 KEY "foo""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Record(RecordAccess {
|
||||||
|
duration: Some(Duration::from_secs(10)),
|
||||||
|
signup: None,
|
||||||
|
signin: None,
|
||||||
|
jwt: JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::Hs384,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Hs384,
|
||||||
|
// Issuer key matches verification key by default in symmetric algorithms.
|
||||||
|
key: "foo".to_string(),
|
||||||
|
// Token duration matches session duration by default.
|
||||||
|
duration: Some(Duration::from_secs(10)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Verification and issuing with JWT are explicitly defined with two different keys.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DB TYPE RECORD DURATION 10s WITH JWT ALGORITHM PS512 KEY "foo" WITH ISSUER KEY "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Record(RecordAccess {
|
||||||
|
duration: Some(Duration::from_secs(10)),
|
||||||
|
signup: None,
|
||||||
|
signin: None,
|
||||||
|
jwt: JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::Ps512,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Ps512,
|
||||||
|
key: "bar".to_string(),
|
||||||
|
// Token duration matches session duration by default.
|
||||||
|
duration: Some(Duration::from_secs(10)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Verification and issuing with JWT are explicitly defined with two different keys. Token duration is explicitly defined.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DB TYPE RECORD DURATION 10s WITH JWT ALGORITHM RS256 KEY 'foo' WITH ISSUER KEY 'bar' DURATION 1m"#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Record(RecordAccess {
|
||||||
|
duration: Some(Duration::from_secs(10)),
|
||||||
|
signup: None,
|
||||||
|
signin: None,
|
||||||
|
jwt: JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::Rs256,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: Some(JwtAccessIssue {
|
||||||
|
alg: Algorithm::Rs256,
|
||||||
|
key: "bar".to_string(),
|
||||||
|
duration: Some(Duration::from_mins(1)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
comment: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_define_access_record_with_jwt() {
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON DATABASE TYPE RECORD WITH JWT ALGORITHM EDDSA KEY "foo" COMMENT "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Db,
|
||||||
|
kind: AccessType::Record(RecordAccess {
|
||||||
|
duration: Some(Duration::from_hours(1)),
|
||||||
|
signup: None,
|
||||||
|
signin: None,
|
||||||
|
jwt: JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::EdDSA,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: None,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
comment: Some(Strand("bar".to_string())),
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -683,9 +1345,6 @@ fn parse_info() {
|
||||||
let res = test_parse!(parse_stmt, "INFO FOR NS").unwrap();
|
let res = test_parse!(parse_stmt, "INFO FOR NS").unwrap();
|
||||||
assert_eq!(res, Statement::Info(InfoStatement::Ns(false)));
|
assert_eq!(res, Statement::Info(InfoStatement::Ns(false)));
|
||||||
|
|
||||||
let res = test_parse!(parse_stmt, "INFO FOR SCOPE scope").unwrap();
|
|
||||||
assert_eq!(res, Statement::Info(InfoStatement::Sc(Ident("scope".to_owned()), false)));
|
|
||||||
|
|
||||||
let res = test_parse!(parse_stmt, "INFO FOR TABLE table").unwrap();
|
let res = test_parse!(parse_stmt, "INFO FOR TABLE table").unwrap();
|
||||||
assert_eq!(res, Statement::Info(InfoStatement::Tb(Ident("table".to_owned()), false)));
|
assert_eq!(res, Statement::Info(InfoStatement::Tb(Ident("table".to_owned()), false)));
|
||||||
|
|
||||||
|
@ -1129,21 +1788,12 @@ fn parse_remove() {
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
let res = test_parse!(parse_stmt, r#"REMOVE TOKEN foo ON SCOPE bar"#).unwrap();
|
let res = test_parse!(parse_stmt, r#"REMOVE ACCESS foo ON DATABASE"#).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
Statement::Remove(RemoveStatement::Token(RemoveTokenStatement {
|
Statement::Remove(RemoveStatement::Access(RemoveAccessStatement {
|
||||||
name: Ident("foo".to_owned()),
|
|
||||||
base: Base::Sc(Ident("bar".to_owned())),
|
|
||||||
if_exists: false,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
let res = test_parse!(parse_stmt, r#"REMOVE SCOPE foo"#).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
res,
|
|
||||||
Statement::Remove(RemoveStatement::Scope(RemoveScopeStatement {
|
|
||||||
name: Ident("foo".to_owned()),
|
name: Ident("foo".to_owned()),
|
||||||
|
base: Base::Db,
|
||||||
if_exists: false,
|
if_exists: false,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
sql::{
|
sql::{
|
||||||
|
access_type::{AccessType, JwtAccess, JwtAccessVerify, JwtAccessVerifyKey, RecordAccess},
|
||||||
block::Entry,
|
block::Entry,
|
||||||
changefeed::ChangeFeed,
|
changefeed::ChangeFeed,
|
||||||
filter::Filter,
|
filter::Filter,
|
||||||
|
@ -8,13 +9,13 @@ use crate::{
|
||||||
statements::{
|
statements::{
|
||||||
analyze::AnalyzeStatement, show::ShowSince, show::ShowStatement, sleep::SleepStatement,
|
analyze::AnalyzeStatement, show::ShowSince, show::ShowStatement, sleep::SleepStatement,
|
||||||
BeginStatement, BreakStatement, CancelStatement, CommitStatement, ContinueStatement,
|
BeginStatement, BreakStatement, CancelStatement, CommitStatement, ContinueStatement,
|
||||||
CreateStatement, DefineAnalyzerStatement, DefineDatabaseStatement,
|
CreateStatement, DefineAccessStatement, DefineAnalyzerStatement,
|
||||||
DefineEventStatement, DefineFieldStatement, DefineFunctionStatement,
|
DefineDatabaseStatement, DefineEventStatement, DefineFieldStatement,
|
||||||
DefineIndexStatement, DefineNamespaceStatement, DefineParamStatement, DefineStatement,
|
DefineFunctionStatement, DefineIndexStatement, DefineNamespaceStatement,
|
||||||
DefineTableStatement, DefineTokenStatement, DeleteStatement, ForeachStatement,
|
DefineParamStatement, DefineStatement, DefineTableStatement, DeleteStatement,
|
||||||
IfelseStatement, InfoStatement, InsertStatement, KillStatement, OutputStatement,
|
ForeachStatement, IfelseStatement, InfoStatement, InsertStatement, KillStatement,
|
||||||
RelateStatement, RemoveFieldStatement, RemoveFunctionStatement, RemoveStatement,
|
OutputStatement, RelateStatement, RemoveFieldStatement, RemoveFunctionStatement,
|
||||||
SelectStatement, SetStatement, ThrowStatement, UpdateStatement,
|
RemoveStatement, SelectStatement, SetStatement, ThrowStatement, UpdateStatement,
|
||||||
},
|
},
|
||||||
tokenizer::Tokenizer,
|
tokenizer::Tokenizer,
|
||||||
Algorithm, Array, Base, Block, Cond, Data, Datetime, Dir, Duration, Edges, Explain,
|
Algorithm, Array, Base, Block, Cond, Data, Datetime, Dir, Duration, Edges, Explain,
|
||||||
|
@ -46,7 +47,7 @@ static SOURCE: &str = r#"
|
||||||
DEFINE FUNCTION fn::foo::bar($a: number, $b: array<bool,3>) {
|
DEFINE FUNCTION fn::foo::bar($a: number, $b: array<bool,3>) {
|
||||||
RETURN a
|
RETURN a
|
||||||
} COMMENT 'test' PERMISSIONS FULL;
|
} COMMENT 'test' PERMISSIONS FULL;
|
||||||
DEFINE TOKEN a ON SCOPE b TYPE EDDSA VALUE "foo" COMMENT "bar";
|
DEFINE ACCESS a ON DATABASE TYPE RECORD WITH JWT ALGORITHM EDDSA KEY "foo" COMMENT "bar";
|
||||||
DEFINE PARAM $a VALUE { a: 1, "b": 3 } PERMISSIONS WHERE null;
|
DEFINE PARAM $a VALUE { a: 1, "b": 3 } PERMISSIONS WHERE null;
|
||||||
DEFINE TABLE name DROP SCHEMAFUL CHANGEFEED 1s PERMISSIONS FOR SELECT WHERE a = 1 AS SELECT foo FROM bar GROUP BY foo;
|
DEFINE TABLE name DROP SCHEMAFUL CHANGEFEED 1s PERMISSIONS FOR SELECT WHERE a = 1 AS SELECT foo FROM bar GROUP BY foo;
|
||||||
DEFINE EVENT event ON TABLE table WHEN null THEN null,none;
|
DEFINE EVENT event ON TABLE table WHEN null THEN null,none;
|
||||||
|
@ -73,7 +74,6 @@ static SOURCE: &str = r#"
|
||||||
IF foo { bar } ELSE IF faz { baz } ELSE { baq };
|
IF foo { bar } ELSE IF faz { baz } ELSE { baq };
|
||||||
INFO FOR ROOT;
|
INFO FOR ROOT;
|
||||||
INFO FOR NAMESPACE;
|
INFO FOR NAMESPACE;
|
||||||
INFO FOR SCOPE scope;
|
|
||||||
INFO FOR USER user ON namespace;
|
INFO FOR USER user ON namespace;
|
||||||
SELECT bar as foo,[1,2],bar OMIT bar FROM ONLY a,1
|
SELECT bar as foo,[1,2],bar OMIT bar FROM ONLY a,1
|
||||||
WITH INDEX index,index_2
|
WITH INDEX index,index_2
|
||||||
|
@ -191,11 +191,21 @@ fn statements() -> Vec<Statement> {
|
||||||
permissions: Permission::Full,
|
permissions: Permission::Full,
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
})),
|
})),
|
||||||
Statement::Define(DefineStatement::Token(DefineTokenStatement {
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
name: Ident("a".to_string()),
|
name: Ident("a".to_string()),
|
||||||
base: Base::Sc(Ident("b".to_string())),
|
base: Base::Db,
|
||||||
kind: Algorithm::EdDSA,
|
kind: AccessType::Record(RecordAccess {
|
||||||
code: "foo".to_string(),
|
duration: Some(Duration::from_hours(1)),
|
||||||
|
signup: None,
|
||||||
|
signin: None,
|
||||||
|
jwt: JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::EdDSA,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: None,
|
||||||
|
},
|
||||||
|
}),
|
||||||
comment: Some(Strand("bar".to_string())),
|
comment: Some(Strand("bar".to_string())),
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
})),
|
})),
|
||||||
|
@ -435,7 +445,6 @@ fn statements() -> Vec<Statement> {
|
||||||
}),
|
}),
|
||||||
Statement::Info(InfoStatement::Root(false)),
|
Statement::Info(InfoStatement::Root(false)),
|
||||||
Statement::Info(InfoStatement::Ns(false)),
|
Statement::Info(InfoStatement::Ns(false)),
|
||||||
Statement::Info(InfoStatement::Sc(Ident("scope".to_owned()), false)),
|
|
||||||
Statement::Info(InfoStatement::User(Ident("user".to_owned()), Some(Base::Ns), false)),
|
Statement::Info(InfoStatement::User(Ident("user".to_owned()), Some(Base::Ns), false)),
|
||||||
Statement::Select(SelectStatement {
|
Statement::Select(SelectStatement {
|
||||||
expr: Fields(
|
expr: Fields(
|
||||||
|
|
|
@ -24,7 +24,9 @@ macro_rules! keyword {
|
||||||
}
|
}
|
||||||
|
|
||||||
keyword! {
|
keyword! {
|
||||||
|
Access => "ACCESS",
|
||||||
After => "AFTER",
|
After => "AFTER",
|
||||||
|
Algorithm => "ALGORITHM",
|
||||||
All => "ALL",
|
All => "ALL",
|
||||||
Analyze => "ANALYZE",
|
Analyze => "ANALYZE",
|
||||||
Analyzer => "ANALYZER",
|
Analyzer => "ANALYZER",
|
||||||
|
@ -93,6 +95,9 @@ keyword! {
|
||||||
Into => "INTO",
|
Into => "INTO",
|
||||||
If => "IF",
|
If => "IF",
|
||||||
Is => "IS",
|
Is => "IS",
|
||||||
|
Issuer => "ISSUER",
|
||||||
|
Jwt => "JWT",
|
||||||
|
Jwks => "JWKS",
|
||||||
Key => "KEY",
|
Key => "KEY",
|
||||||
KeepPrunedConnections => "KEEP_PRUNED_CONNECTIONS",
|
KeepPrunedConnections => "KEEP_PRUNED_CONNECTIONS",
|
||||||
Kill => "KILL",
|
Kill => "KILL",
|
||||||
|
@ -169,6 +174,7 @@ keyword! {
|
||||||
Unset => "UNSET",
|
Unset => "UNSET",
|
||||||
Update => "UPDATE",
|
Update => "UPDATE",
|
||||||
Uppercase => "UPPERCASE",
|
Uppercase => "UPPERCASE",
|
||||||
|
Url => "URL",
|
||||||
Use => "USE",
|
Use => "USE",
|
||||||
User => "USER",
|
User => "USER",
|
||||||
Value => "VALUE",
|
Value => "VALUE",
|
||||||
|
|
|
@ -365,8 +365,8 @@ impl TokenKind {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn algorithm_as_str(algo: Algorithm) -> &'static str {
|
fn algorithm_as_str(alg: Algorithm) -> &'static str {
|
||||||
match algo {
|
match alg {
|
||||||
Algorithm::EdDSA => "EDDSA",
|
Algorithm::EdDSA => "EDDSA",
|
||||||
Algorithm::Es256 => "ES256",
|
Algorithm::Es256 => "ES256",
|
||||||
Algorithm::Es384 => "ES384",
|
Algorithm::Es384 => "ES384",
|
||||||
|
@ -380,7 +380,6 @@ impl TokenKind {
|
||||||
Algorithm::Rs256 => "RS256",
|
Algorithm::Rs256 => "RS256",
|
||||||
Algorithm::Rs384 => "RS384",
|
Algorithm::Rs384 => "RS384",
|
||||||
Algorithm::Rs512 => "RS512",
|
Algorithm::Rs512 => "RS512",
|
||||||
Algorithm::Jwks => "JWKS",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
151
lib/fuzz/Cargo.lock
generated
151
lib/fuzz/Cargo.lock
generated
|
@ -227,12 +227,6 @@ dependencies = [
|
||||||
"critical-section",
|
"critical-section",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atomic-waker"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -531,6 +525,33 @@ dependencies = [
|
||||||
"windows-targets 0.52.4",
|
"windows-targets 0.52.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||||
|
dependencies = [
|
||||||
|
"ciborium-io",
|
||||||
|
"ciborium-ll",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium-io"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium-ll"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||||
|
dependencies = [
|
||||||
|
"ciborium-io",
|
||||||
|
"half",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
@ -732,9 +753,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "echodb"
|
name = "echodb"
|
||||||
version = "0.4.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "312221c0bb46e82cd250c818404ef9dce769a4d5a62915c0249b577762eec34a"
|
checksum = "1ac31e38aeac770dd01b9d6c9ab2a6d7f025815f71105911cf6de073a5db8ee1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"imbl",
|
"imbl",
|
||||||
|
@ -1063,6 +1084,16 @@ version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hash32"
|
name = "hash32"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -1622,6 +1653,12 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "path-clean"
|
name = "path-clean"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -1685,6 +1722,40 @@ dependencies = [
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||||
|
dependencies = [
|
||||||
|
"phf_macros",
|
||||||
|
"phf_shared 0.11.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_generator"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||||
|
dependencies = [
|
||||||
|
"phf_shared 0.11.2",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_macros"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
|
||||||
|
dependencies = [
|
||||||
|
"phf_generator",
|
||||||
|
"phf_shared 0.11.2",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.58",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -1694,6 +1765,16 @@ dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_shared"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||||
|
dependencies = [
|
||||||
|
"siphasher",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pico-args"
|
name = "pico-args"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -1927,14 +2008,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reblessive"
|
name = "reblessive"
|
||||||
version = "0.3.2"
|
version = "0.3.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb88832db7cfbac349e0276a84d4fbbcdb9cfe8069affbc765d8fd012265ba45"
|
checksum = "4149deda5bd21e0f6ccaa2f907cd542541521dead5861bc51bebdf2af4acaf2a"
|
||||||
dependencies = [
|
|
||||||
"atomic-waker",
|
|
||||||
"pin-project-lite",
|
|
||||||
"pin-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
|
@ -2019,9 +2095,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revision"
|
name = "revision"
|
||||||
version = "0.5.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87eb86913082f8976b06d07a59f17df9120e6f38b882cf3fc5a45b4499e224b6"
|
checksum = "588784c1d9453cfd2ce1b7aff06c903513677cf0e63779a0a3085ee8a44f5b17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -2037,9 +2113,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revision-derive"
|
name = "revision-derive"
|
||||||
version = "0.5.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf996fc5f61f1dbec35799b5c00c6dda12e8862e8cb782ed24e10d0292e60ed3"
|
checksum = "854ff0b6794d4e0aab5e4486870941caefe9f258e63cad2f21b49a6302377c85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
|
@ -2107,6 +2183,27 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmp"
|
||||||
|
version = "0.8.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmpv"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "58450723cd9ee93273ce44a20b6ec4efe17f8ed2e3631474387bfdecf18bb2a9"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"rmp",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roaring"
|
name = "roaring"
|
||||||
version = "0.10.3"
|
version = "0.10.3"
|
||||||
|
@ -2522,7 +2619,7 @@ dependencies = [
|
||||||
"new_debug_unreachable",
|
"new_debug_unreachable",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"phf_shared",
|
"phf_shared 0.10.0",
|
||||||
"precomputed-hash",
|
"precomputed-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2554,6 +2651,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"path-clean",
|
"path-clean",
|
||||||
"pharos",
|
"pharos",
|
||||||
|
"reblessive",
|
||||||
"revision",
|
"revision",
|
||||||
"ring 0.17.8",
|
"ring 0.17.8",
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
|
@ -2588,6 +2686,7 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"cedar-policy",
|
"cedar-policy",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"ciborium",
|
||||||
"deunicode",
|
"deunicode",
|
||||||
"dmp",
|
"dmp",
|
||||||
"echodb",
|
"echodb",
|
||||||
|
@ -2607,6 +2706,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pbkdf2",
|
"pbkdf2",
|
||||||
"pharos",
|
"pharos",
|
||||||
|
"phf",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"quick_cache",
|
"quick_cache",
|
||||||
"radix_trie",
|
"radix_trie",
|
||||||
|
@ -2616,6 +2716,7 @@ dependencies = [
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
"revision",
|
"revision",
|
||||||
"ring 0.17.8",
|
"ring 0.17.8",
|
||||||
|
"rmpv",
|
||||||
"roaring",
|
"roaring",
|
||||||
"rust-stemmers",
|
"rust-stemmers",
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
|
@ -2634,6 +2735,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"trice",
|
"trice",
|
||||||
"ulid",
|
"ulid",
|
||||||
|
"unicase",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
@ -2918,6 +3020,15 @@ dependencies = [
|
||||||
"web-time",
|
"web-time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
"ACCESS"
|
||||||
"AFTER"
|
"AFTER"
|
||||||
|
"ALGORITHM"
|
||||||
"ALL"
|
"ALL"
|
||||||
"ALLINSIDE"
|
"ALLINSIDE"
|
||||||
"AND"
|
"AND"
|
||||||
|
@ -61,6 +63,9 @@
|
||||||
"INTERSECTS"
|
"INTERSECTS"
|
||||||
"INTO"
|
"INTO"
|
||||||
"IS"
|
"IS"
|
||||||
|
"JWKS"
|
||||||
|
"JWT"
|
||||||
|
"KEY"
|
||||||
"KILL"
|
"KILL"
|
||||||
"KV"
|
"KV"
|
||||||
"LET"
|
"LET"
|
||||||
|
@ -89,15 +94,14 @@
|
||||||
"PATH"
|
"PATH"
|
||||||
"PERMISSIONS"
|
"PERMISSIONS"
|
||||||
"PI"
|
"PI"
|
||||||
|
"RECORD"
|
||||||
"RELATE"
|
"RELATE"
|
||||||
"REMOVE"
|
"REMOVE"
|
||||||
"REPLACE"
|
"REPLACE"
|
||||||
"RETURN"
|
"RETURN"
|
||||||
"SC"
|
|
||||||
"SCHEMAFUL"
|
"SCHEMAFUL"
|
||||||
"SCHEMAFULL"
|
"SCHEMAFULL"
|
||||||
"SCHEMALESS"
|
"SCHEMALESS"
|
||||||
"SCOPE"
|
|
||||||
"SELECT"
|
"SELECT"
|
||||||
"SESSION"
|
"SESSION"
|
||||||
"SET"
|
"SET"
|
||||||
|
@ -116,14 +120,13 @@
|
||||||
"TB"
|
"TB"
|
||||||
"THEN"
|
"THEN"
|
||||||
"TIMEOUT"
|
"TIMEOUT"
|
||||||
"TK"
|
|
||||||
"TOKEN"
|
|
||||||
"TRANSACTION"
|
"TRANSACTION"
|
||||||
"TRUE"
|
"TRUE"
|
||||||
"TYPE"
|
"TYPE"
|
||||||
"UNIQUE"
|
"UNIQUE"
|
||||||
"UPDATE"
|
"UPDATE"
|
||||||
"UPPERCASE"
|
"UPPERCASE"
|
||||||
|
"URL"
|
||||||
"USE"
|
"USE"
|
||||||
"USER"
|
"USER"
|
||||||
"VALUE"
|
"VALUE"
|
||||||
|
@ -277,12 +280,12 @@
|
||||||
"search::offsets("
|
"search::offsets("
|
||||||
"session"
|
"session"
|
||||||
"session::"
|
"session::"
|
||||||
|
"session::ac("
|
||||||
"session::db("
|
"session::db("
|
||||||
"session::id("
|
"session::id("
|
||||||
"session::ip("
|
"session::ip("
|
||||||
"session::ns("
|
"session::ns("
|
||||||
"session::origin("
|
"session::origin("
|
||||||
"session::sc"
|
|
||||||
# Sleep is just going to slow the fuzzer down
|
# Sleep is just going to slow the fuzzer down
|
||||||
# "sleep("
|
# "sleep("
|
||||||
"string"
|
"string"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"AFTER"
|
"AFTER"
|
||||||
|
"ALGORITHM"
|
||||||
"ALL"
|
"ALL"
|
||||||
"ALLINSIDE"
|
"ALLINSIDE"
|
||||||
"AND"
|
"AND"
|
||||||
|
@ -61,6 +62,9 @@
|
||||||
"INTERSECTS"
|
"INTERSECTS"
|
||||||
"INTO"
|
"INTO"
|
||||||
"IS"
|
"IS"
|
||||||
|
"JWKS"
|
||||||
|
"JWT"
|
||||||
|
"KEY"
|
||||||
"KILL"
|
"KILL"
|
||||||
"KV"
|
"KV"
|
||||||
"LET"
|
"LET"
|
||||||
|
@ -89,15 +93,14 @@
|
||||||
"PATH"
|
"PATH"
|
||||||
"PERMISSIONS"
|
"PERMISSIONS"
|
||||||
"PI"
|
"PI"
|
||||||
|
"RECORD"
|
||||||
"RELATE"
|
"RELATE"
|
||||||
"REMOVE"
|
"REMOVE"
|
||||||
"REPLACE"
|
"REPLACE"
|
||||||
"RETURN"
|
"RETURN"
|
||||||
"SC"
|
|
||||||
"SCHEMAFUL"
|
"SCHEMAFUL"
|
||||||
"SCHEMAFULL"
|
"SCHEMAFULL"
|
||||||
"SCHEMALESS"
|
"SCHEMALESS"
|
||||||
"SCOPE"
|
|
||||||
"SELECT"
|
"SELECT"
|
||||||
"SESSION"
|
"SESSION"
|
||||||
"SET"
|
"SET"
|
||||||
|
@ -116,14 +119,13 @@
|
||||||
"TB"
|
"TB"
|
||||||
"THEN"
|
"THEN"
|
||||||
"TIMEOUT"
|
"TIMEOUT"
|
||||||
"TK"
|
|
||||||
"TOKEN"
|
|
||||||
"TRANSACTION"
|
"TRANSACTION"
|
||||||
"TRUE"
|
"TRUE"
|
||||||
"TYPE"
|
"TYPE"
|
||||||
"UNIQUE"
|
"UNIQUE"
|
||||||
"UPDATE"
|
"UPDATE"
|
||||||
"UPPERCASE"
|
"UPPERCASE"
|
||||||
|
"URL"
|
||||||
"USE"
|
"USE"
|
||||||
"USER"
|
"USER"
|
||||||
"VALUE"
|
"VALUE"
|
||||||
|
@ -277,12 +279,12 @@
|
||||||
"search::offsets("
|
"search::offsets("
|
||||||
"session"
|
"session"
|
||||||
"session::"
|
"session::"
|
||||||
|
"session::ac("
|
||||||
"session::db("
|
"session::db("
|
||||||
"session::id("
|
"session::id("
|
||||||
"session::ip("
|
"session::ip("
|
||||||
"session::ns("
|
"session::ns("
|
||||||
"session::origin("
|
"session::origin("
|
||||||
"session::sc"
|
|
||||||
"sleep("
|
"sleep("
|
||||||
"string"
|
"string"
|
||||||
"string::concat("
|
"string::concat("
|
||||||
|
|
|
@ -393,7 +393,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs up a user to a specific authentication scope
|
/// Signs up a user with a specific record access method
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -401,7 +401,7 @@ where
|
||||||
/// use serde::Serialize;
|
/// use serde::Serialize;
|
||||||
/// use surrealdb::sql;
|
/// use surrealdb::sql;
|
||||||
/// use surrealdb::opt::auth::Root;
|
/// use surrealdb::opt::auth::Root;
|
||||||
/// use surrealdb::opt::auth::Scope;
|
/// use surrealdb::opt::auth::Record;
|
||||||
///
|
///
|
||||||
/// #[derive(Debug, Serialize)]
|
/// #[derive(Debug, Serialize)]
|
||||||
/// struct AuthParams {
|
/// struct AuthParams {
|
||||||
|
@ -423,19 +423,19 @@ where
|
||||||
/// // Select the namespace/database to use
|
/// // Select the namespace/database to use
|
||||||
/// db.use_ns("namespace").use_db("database").await?;
|
/// db.use_ns("namespace").use_db("database").await?;
|
||||||
///
|
///
|
||||||
/// // Define the scope
|
/// // Define the user record access
|
||||||
/// let sql = r#"
|
/// let sql = r#"
|
||||||
/// DEFINE SCOPE user_scope SESSION 24h
|
/// DEFINE ACCESS user_access ON DATABASE TYPE RECORD DURATION 24h
|
||||||
/// SIGNUP ( CREATE user SET email = $email, password = crypto::argon2::generate($password) )
|
/// SIGNUP ( CREATE user SET email = $email, password = crypto::argon2::generate($password) )
|
||||||
/// SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password) )
|
/// SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password) )
|
||||||
/// "#;
|
/// "#;
|
||||||
/// db.query(sql).await?.check()?;
|
/// db.query(sql).await?.check()?;
|
||||||
///
|
///
|
||||||
/// // Sign a user up
|
/// // Sign a user up
|
||||||
/// db.signup(Scope {
|
/// db.signup(Record {
|
||||||
/// namespace: "namespace",
|
/// namespace: "namespace",
|
||||||
/// database: "database",
|
/// database: "database",
|
||||||
/// scope: "user_scope",
|
/// access: "user_access",
|
||||||
/// params: AuthParams {
|
/// params: AuthParams {
|
||||||
/// email: "john.doe@example.com".into(),
|
/// email: "john.doe@example.com".into(),
|
||||||
/// password: "password123".into(),
|
/// password: "password123".into(),
|
||||||
|
@ -453,7 +453,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs this connection in to a specific authentication scope
|
/// Signs this connection in to a specific authentication level
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -530,12 +530,12 @@ where
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Scope signin
|
/// Record signin
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use serde::Serialize;
|
/// use serde::Serialize;
|
||||||
/// use surrealdb::opt::auth::Root;
|
/// use surrealdb::opt::auth::Root;
|
||||||
/// use surrealdb::opt::auth::Scope;
|
/// use surrealdb::opt::auth::Record;
|
||||||
///
|
///
|
||||||
/// #[derive(Debug, Serialize)]
|
/// #[derive(Debug, Serialize)]
|
||||||
/// struct AuthParams {
|
/// struct AuthParams {
|
||||||
|
@ -551,10 +551,10 @@ where
|
||||||
/// db.use_ns("namespace").use_db("database").await?;
|
/// db.use_ns("namespace").use_db("database").await?;
|
||||||
///
|
///
|
||||||
/// // Sign a user in
|
/// // Sign a user in
|
||||||
/// db.signin(Scope {
|
/// db.signin(Record {
|
||||||
/// namespace: "namespace",
|
/// namespace: "namespace",
|
||||||
/// database: "database",
|
/// database: "database",
|
||||||
/// scope: "user_scope",
|
/// access: "user_access",
|
||||||
/// params: AuthParams {
|
/// params: AuthParams {
|
||||||
/// email: "john.doe@example.com".into(),
|
/// email: "john.doe@example.com".into(),
|
||||||
/// password: "password123".into(),
|
/// password: "password123".into(),
|
||||||
|
|
|
@ -9,8 +9,8 @@ use crate::api::method::tests::types::AuthParams;
|
||||||
use crate::api::opt::auth::Database;
|
use crate::api::opt::auth::Database;
|
||||||
use crate::api::opt::auth::Jwt;
|
use crate::api::opt::auth::Jwt;
|
||||||
use crate::api::opt::auth::Namespace;
|
use crate::api::opt::auth::Namespace;
|
||||||
|
use crate::api::opt::auth::Record;
|
||||||
use crate::api::opt::auth::Root;
|
use crate::api::opt::auth::Root;
|
||||||
use crate::api::opt::auth::Scope;
|
|
||||||
use crate::api::opt::PatchOp;
|
use crate::api::opt::PatchOp;
|
||||||
use crate::api::Response as QueryResponse;
|
use crate::api::Response as QueryResponse;
|
||||||
use crate::api::Surreal;
|
use crate::api::Surreal;
|
||||||
|
@ -42,10 +42,10 @@ async fn api() {
|
||||||
|
|
||||||
// signup
|
// signup
|
||||||
let _: Jwt = DB
|
let _: Jwt = DB
|
||||||
.signup(Scope {
|
.signup(Record {
|
||||||
namespace: "test-ns",
|
namespace: "test-ns",
|
||||||
database: "test-db",
|
database: "test-db",
|
||||||
scope: "scope",
|
access: "access",
|
||||||
params: AuthParams {},
|
params: AuthParams {},
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -77,10 +77,10 @@ async fn api() {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let _: Jwt = DB
|
let _: Jwt = DB
|
||||||
.signin(Scope {
|
.signin(Record {
|
||||||
namespace: "test-ns",
|
namespace: "test-ns",
|
||||||
database: "test-db",
|
database: "test-db",
|
||||||
scope: "scope",
|
access: "access",
|
||||||
params: AuthParams {},
|
params: AuthParams {},
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -63,24 +63,24 @@ pub struct Database<'a> {
|
||||||
|
|
||||||
impl Credentials<Signin, Jwt> for Database<'_> {}
|
impl Credentials<Signin, Jwt> for Database<'_> {}
|
||||||
|
|
||||||
/// Credentials for the scope user
|
/// Credentials for the record user
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct Scope<'a, P> {
|
pub struct Record<'a, P> {
|
||||||
/// The namespace the user has access to
|
/// The namespace the user has access to
|
||||||
#[serde(rename = "ns")]
|
#[serde(rename = "ns")]
|
||||||
pub namespace: &'a str,
|
pub namespace: &'a str,
|
||||||
/// The database the user has access to
|
/// The database the user has access to
|
||||||
#[serde(rename = "db")]
|
#[serde(rename = "db")]
|
||||||
pub database: &'a str,
|
pub database: &'a str,
|
||||||
/// The scope to use for signin and signup
|
/// The access method to use for signin and signup
|
||||||
#[serde(rename = "sc")]
|
#[serde(rename = "ac")]
|
||||||
pub scope: &'a str,
|
pub access: &'a str,
|
||||||
/// The additional params to use
|
/// The additional params to use
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub params: P,
|
pub params: P,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, P> Credentials<T, Jwt> for Scope<'_, P> where P: Serialize {}
|
impl<T, P> Credentials<T, Jwt> for Record<'_, P> where P: Serialize {}
|
||||||
|
|
||||||
/// A JSON Web Token for authenticating with the server.
|
/// A JSON Web Token for authenticating with the server.
|
||||||
///
|
///
|
||||||
|
|
|
@ -18,8 +18,8 @@ mod api_integration {
|
||||||
use surrealdb::opt::auth::Database;
|
use surrealdb::opt::auth::Database;
|
||||||
use surrealdb::opt::auth::Jwt;
|
use surrealdb::opt::auth::Jwt;
|
||||||
use surrealdb::opt::auth::Namespace;
|
use surrealdb::opt::auth::Namespace;
|
||||||
|
use surrealdb::opt::auth::Record as RecordAccess;
|
||||||
use surrealdb::opt::auth::Root;
|
use surrealdb::opt::auth::Root;
|
||||||
use surrealdb::opt::auth::Scope;
|
|
||||||
use surrealdb::opt::Config;
|
use surrealdb::opt::Config;
|
||||||
use surrealdb::opt::PatchOp;
|
use surrealdb::opt::PatchOp;
|
||||||
use surrealdb::opt::Resource;
|
use surrealdb::opt::Resource;
|
||||||
|
|
|
@ -52,14 +52,14 @@ async fn invalidate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test(tokio::test)]
|
#[test_log::test(tokio::test)]
|
||||||
async fn signup_scope() {
|
async fn signup_record() {
|
||||||
let (permit, db) = new_db().await;
|
let (permit, db) = new_db().await;
|
||||||
let database = Ulid::new().to_string();
|
let database = Ulid::new().to_string();
|
||||||
db.use_ns(NS).use_db(&database).await.unwrap();
|
db.use_ns(NS).use_db(&database).await.unwrap();
|
||||||
let scope = Ulid::new().to_string();
|
let access = Ulid::new().to_string();
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
"
|
"
|
||||||
DEFINE SCOPE `{scope}` SESSION 1s
|
DEFINE ACCESS `{access}` ON DB TYPE RECORD DURATION 1s
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
"
|
"
|
||||||
|
@ -67,10 +67,10 @@ async fn signup_scope() {
|
||||||
let response = db.query(sql).await.unwrap();
|
let response = db.query(sql).await.unwrap();
|
||||||
drop(permit);
|
drop(permit);
|
||||||
response.check().unwrap();
|
response.check().unwrap();
|
||||||
db.signup(Scope {
|
db.signup(RecordAccess {
|
||||||
namespace: NS,
|
namespace: NS,
|
||||||
database: &database,
|
database: &database,
|
||||||
scope: &scope,
|
access: &access,
|
||||||
params: AuthParams {
|
params: AuthParams {
|
||||||
email: "john.doe@example.com",
|
email: "john.doe@example.com",
|
||||||
pass: "password123",
|
pass: "password123",
|
||||||
|
@ -121,16 +121,16 @@ async fn signin_db() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test(tokio::test)]
|
#[test_log::test(tokio::test)]
|
||||||
async fn signin_scope() {
|
async fn signin_record() {
|
||||||
let (permit, db) = new_db().await;
|
let (permit, db) = new_db().await;
|
||||||
let database = Ulid::new().to_string();
|
let database = Ulid::new().to_string();
|
||||||
db.use_ns(NS).use_db(&database).await.unwrap();
|
db.use_ns(NS).use_db(&database).await.unwrap();
|
||||||
let scope = Ulid::new().to_string();
|
let access = Ulid::new().to_string();
|
||||||
let email = format!("{scope}@example.com");
|
let email = format!("{access}@example.com");
|
||||||
let pass = "password123";
|
let pass = "password123";
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
"
|
"
|
||||||
DEFINE SCOPE `{scope}` SESSION 1s
|
DEFINE ACCESS `{access}` ON DB TYPE RECORD DURATION 1s
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
"
|
"
|
||||||
|
@ -138,10 +138,10 @@ async fn signin_scope() {
|
||||||
let response = db.query(sql).await.unwrap();
|
let response = db.query(sql).await.unwrap();
|
||||||
drop(permit);
|
drop(permit);
|
||||||
response.check().unwrap();
|
response.check().unwrap();
|
||||||
db.signup(Scope {
|
db.signup(RecordAccess {
|
||||||
namespace: NS,
|
namespace: NS,
|
||||||
database: &database,
|
database: &database,
|
||||||
scope: &scope,
|
access: &access,
|
||||||
params: AuthParams {
|
params: AuthParams {
|
||||||
pass,
|
pass,
|
||||||
email: &email,
|
email: &email,
|
||||||
|
@ -149,10 +149,10 @@ async fn signin_scope() {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
db.signin(Scope {
|
db.signin(RecordAccess {
|
||||||
namespace: NS,
|
namespace: NS,
|
||||||
database: &database,
|
database: &database,
|
||||||
scope: &scope,
|
access: &access,
|
||||||
params: AuthParams {
|
params: AuthParams {
|
||||||
pass,
|
pass,
|
||||||
email: &email,
|
email: &email,
|
||||||
|
@ -163,16 +163,16 @@ async fn signin_scope() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test(tokio::test)]
|
#[test_log::test(tokio::test)]
|
||||||
async fn scope_throws_error() {
|
async fn record_access_throws_error() {
|
||||||
let (permit, db) = new_db().await;
|
let (permit, db) = new_db().await;
|
||||||
let database = Ulid::new().to_string();
|
let database = Ulid::new().to_string();
|
||||||
db.use_ns(NS).use_db(&database).await.unwrap();
|
db.use_ns(NS).use_db(&database).await.unwrap();
|
||||||
let scope = Ulid::new().to_string();
|
let access = Ulid::new().to_string();
|
||||||
let email = format!("{scope}@example.com");
|
let email = format!("{access}@example.com");
|
||||||
let pass = "password123";
|
let pass = "password123";
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
"
|
"
|
||||||
DEFINE SCOPE `{scope}` SESSION 1s
|
DEFINE ACCESS `{access}` ON DB TYPE RECORD DURATION 1s
|
||||||
SIGNUP {{ THROW 'signup_thrown_error' }}
|
SIGNUP {{ THROW 'signup_thrown_error' }}
|
||||||
SIGNIN {{ THROW 'signin_thrown_error' }}
|
SIGNIN {{ THROW 'signin_thrown_error' }}
|
||||||
"
|
"
|
||||||
|
@ -182,10 +182,10 @@ async fn scope_throws_error() {
|
||||||
response.check().unwrap();
|
response.check().unwrap();
|
||||||
|
|
||||||
match db
|
match db
|
||||||
.signup(Scope {
|
.signup(RecordAccess {
|
||||||
namespace: NS,
|
namespace: NS,
|
||||||
database: &database,
|
database: &database,
|
||||||
scope: &scope,
|
access: &access,
|
||||||
params: AuthParams {
|
params: AuthParams {
|
||||||
pass,
|
pass,
|
||||||
email: &email,
|
email: &email,
|
||||||
|
@ -203,10 +203,10 @@ async fn scope_throws_error() {
|
||||||
};
|
};
|
||||||
|
|
||||||
match db
|
match db
|
||||||
.signin(Scope {
|
.signin(RecordAccess {
|
||||||
namespace: NS,
|
namespace: NS,
|
||||||
database: &database,
|
database: &database,
|
||||||
scope: &scope,
|
access: &access,
|
||||||
params: AuthParams {
|
params: AuthParams {
|
||||||
pass,
|
pass,
|
||||||
email: &email,
|
email: &email,
|
||||||
|
@ -225,16 +225,16 @@ async fn scope_throws_error() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test(tokio::test)]
|
#[test_log::test(tokio::test)]
|
||||||
async fn scope_invalid_query() {
|
async fn record_access_invalid_query() {
|
||||||
let (permit, db) = new_db().await;
|
let (permit, db) = new_db().await;
|
||||||
let database = Ulid::new().to_string();
|
let database = Ulid::new().to_string();
|
||||||
db.use_ns(NS).use_db(&database).await.unwrap();
|
db.use_ns(NS).use_db(&database).await.unwrap();
|
||||||
let scope = Ulid::new().to_string();
|
let access = Ulid::new().to_string();
|
||||||
let email = format!("{scope}@example.com");
|
let email = format!("{access}@example.com");
|
||||||
let pass = "password123";
|
let pass = "password123";
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
"
|
"
|
||||||
DEFINE SCOPE `{scope}` SESSION 1s
|
DEFINE ACCESS `{access}` ON DB TYPE RECORD DURATION 1s
|
||||||
SIGNUP {{ SELECT * FROM ONLY [1, 2] }}
|
SIGNUP {{ SELECT * FROM ONLY [1, 2] }}
|
||||||
SIGNIN {{ SELECT * FROM ONLY [1, 2] }}
|
SIGNIN {{ SELECT * FROM ONLY [1, 2] }}
|
||||||
"
|
"
|
||||||
|
@ -244,10 +244,10 @@ async fn scope_invalid_query() {
|
||||||
response.check().unwrap();
|
response.check().unwrap();
|
||||||
|
|
||||||
match db
|
match db
|
||||||
.signup(Scope {
|
.signup(RecordAccess {
|
||||||
namespace: NS,
|
namespace: NS,
|
||||||
database: &database,
|
database: &database,
|
||||||
scope: &scope,
|
access: &access,
|
||||||
params: AuthParams {
|
params: AuthParams {
|
||||||
pass,
|
pass,
|
||||||
email: &email,
|
email: &email,
|
||||||
|
@ -255,9 +255,12 @@ async fn scope_invalid_query() {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Err(Error::Db(surrealdb::err::Error::SignupQueryFailed)) => (),
|
Err(Error::Db(surrealdb::err::Error::AccessRecordSignupQueryFailed)) => (),
|
||||||
Err(Error::Api(surrealdb::error::Api::Query(e))) => {
|
Err(Error::Api(surrealdb::error::Api::Query(e))) => {
|
||||||
assert_eq!(e, "There was a problem with the database: The signup query failed")
|
assert_eq!(
|
||||||
|
e,
|
||||||
|
"There was a problem with the database: The record access signup query failed"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Err(Error::Api(surrealdb::error::Api::Http(e))) => assert_eq!(
|
Err(Error::Api(surrealdb::error::Api::Http(e))) => assert_eq!(
|
||||||
e,
|
e,
|
||||||
|
@ -267,10 +270,10 @@ async fn scope_invalid_query() {
|
||||||
};
|
};
|
||||||
|
|
||||||
match db
|
match db
|
||||||
.signin(Scope {
|
.signin(RecordAccess {
|
||||||
namespace: NS,
|
namespace: NS,
|
||||||
database: &database,
|
database: &database,
|
||||||
scope: &scope,
|
access: &access,
|
||||||
params: AuthParams {
|
params: AuthParams {
|
||||||
pass,
|
pass,
|
||||||
email: &email,
|
email: &email,
|
||||||
|
@ -278,9 +281,12 @@ async fn scope_invalid_query() {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Err(Error::Db(surrealdb::err::Error::SigninQueryFailed)) => (),
|
Err(Error::Db(surrealdb::err::Error::AccessRecordSigninQueryFailed)) => (),
|
||||||
Err(Error::Api(surrealdb::error::Api::Query(e))) => {
|
Err(Error::Api(surrealdb::error::Api::Query(e))) => {
|
||||||
assert_eq!(e, "There was a problem with the database: The signin query failed")
|
assert_eq!(
|
||||||
|
e,
|
||||||
|
"There was a problem with the database: The record access signin query failed"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Err(Error::Api(surrealdb::error::Api::Http(e))) => assert_eq!(
|
Err(Error::Api(surrealdb::error::Api::Http(e))) => assert_eq!(
|
||||||
e,
|
e,
|
||||||
|
|
|
@ -236,7 +236,7 @@ async fn create_or_insert_with_permissions() -> Result<(), Error> {
|
||||||
CREATE demo SET id = demo:one;
|
CREATE demo SET id = demo:one;
|
||||||
INSERT INTO demo (id) VALUES (demo:two);
|
INSERT INTO demo (id) VALUES (demo:two);
|
||||||
";
|
";
|
||||||
let ses = Session::for_scope("test", "test", "test", Thing::from(("user", "test")).into());
|
let ses = Session::for_record("test", "test", "test", Thing::from(("user", "test")).into());
|
||||||
let res = &mut dbs.execute(sql, &ses, None).await?;
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
assert_eq!(res.len(), 2);
|
assert_eq!(res.len(), 2);
|
||||||
//
|
//
|
||||||
|
|
|
@ -55,8 +55,8 @@ async fn define_statement_database() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
databases: { test: 'DEFINE DATABASE test' },
|
databases: { test: 'DEFINE DATABASE test' },
|
||||||
tokens: {},
|
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
);
|
);
|
||||||
|
@ -84,12 +84,11 @@ async fn define_statement_function() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: { test: 'DEFINE FUNCTION fn::test($first: string, $last: string) { RETURN $first + $last; } PERMISSIONS FULL' },
|
functions: { test: 'DEFINE FUNCTION fn::test($first: string, $last: string) { RETURN $first + $last; } PERMISSIONS FULL' },
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: {},
|
tables: {},
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
@ -116,12 +115,11 @@ async fn define_statement_table_drop() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: { test: 'DEFINE TABLE test TYPE ANY DROP SCHEMALESS PERMISSIONS NONE' },
|
tables: { test: 'DEFINE TABLE test TYPE ANY DROP SCHEMALESS PERMISSIONS NONE' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
@ -148,12 +146,11 @@ async fn define_statement_table_schemaless() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE' },
|
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
@ -184,12 +181,11 @@ async fn define_statement_table_schemafull() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE' },
|
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
@ -216,12 +212,11 @@ async fn define_statement_table_schemaful() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE' },
|
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
@ -256,12 +251,11 @@ async fn define_statement_table_foreigntable() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: {
|
tables: {
|
||||||
test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE',
|
test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE',
|
||||||
view: 'DEFINE TABLE view TYPE ANY SCHEMALESS AS SELECT count() FROM test GROUP ALL PERMISSIONS NONE',
|
view: 'DEFINE TABLE view TYPE ANY SCHEMALESS AS SELECT count() FROM test GROUP ALL PERMISSIONS NONE',
|
||||||
|
@ -289,12 +283,11 @@ async fn define_statement_table_foreigntable() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: {
|
tables: {
|
||||||
test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE',
|
test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE',
|
||||||
},
|
},
|
||||||
|
@ -1267,18 +1260,17 @@ async fn define_statement_analyzer() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
r#"{
|
r#"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {
|
analyzers: {
|
||||||
autocomplete: 'DEFINE ANALYZER autocomplete FILTERS LOWERCASE,EDGENGRAM(2,10)',
|
autocomplete: 'DEFINE ANALYZER autocomplete FILTERS LOWERCASE,EDGENGRAM(2,10)',
|
||||||
english: 'DEFINE ANALYZER english TOKENIZERS BLANK,CLASS FILTERS LOWERCASE,SNOWBALL(ENGLISH)',
|
english: 'DEFINE ANALYZER english TOKENIZERS BLANK,CLASS FILTERS LOWERCASE,SNOWBALL(ENGLISH)',
|
||||||
htmlAnalyzer: 'DEFINE ANALYZER htmlAnalyzer FUNCTION fn::stripHtml TOKENIZERS BLANK,CLASS'
|
htmlAnalyzer: 'DEFINE ANALYZER htmlAnalyzer FUNCTION fn::stripHtml TOKENIZERS BLANK,CLASS'
|
||||||
},
|
},
|
||||||
tokens: {},
|
|
||||||
functions: {
|
functions: {
|
||||||
stripHtml: "DEFINE FUNCTION fn::stripHtml($html: string) { RETURN string::replace($html, /<[^>]*>/, ''); } PERMISSIONS FULL"
|
stripHtml: "DEFINE FUNCTION fn::stripHtml($html: string) { RETURN string::replace($html, /<[^>]*>/, ''); } PERMISSIONS FULL"
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: {},
|
tables: {},
|
||||||
users: {},
|
users: {},
|
||||||
}"#,
|
}"#,
|
||||||
|
@ -1557,8 +1549,8 @@ async fn permissions_checks_define_db() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ databases: { DB: 'DEFINE DATABASE DB' }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, databases: { DB: 'DEFINE DATABASE DB' }, users: { } }"],
|
||||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -1599,8 +1591,8 @@ async fn permissions_checks_define_function() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; } PERMISSIONS FULL\" }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; } PERMISSIONS FULL\" }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -1641,8 +1633,8 @@ async fn permissions_checks_define_analyzer() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -1674,17 +1666,17 @@ async fn permissions_checks_define_analyzer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn permissions_checks_define_token_ns() {
|
async fn permissions_checks_define_access_ns() {
|
||||||
let scenario = HashMap::from([
|
let scenario = HashMap::from([
|
||||||
("prepare", ""),
|
("prepare", ""),
|
||||||
("test", "DEFINE TOKEN token ON NS TYPE HS512 VALUE 'secret'"),
|
("test", "DEFINE ACCESS access ON NS TYPE JWT ALGORITHM HS512 KEY 'secret'"),
|
||||||
("check", "INFO FOR NS"),
|
("check", "INFO FOR NS"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ databases: { }, tokens: { token: \"DEFINE TOKEN token ON NAMESPACE TYPE HS512 VALUE 'secret'\" }, users: { } }"],
|
vec!["{ accesses: { access: \"DEFINE ACCESS access ON NAMESPACE TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, databases: { }, users: { } }"],
|
||||||
vec!["{ databases: { }, tokens: { }, users: { } }"]
|
vec!["{ accesses: { }, databases: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -1716,17 +1708,17 @@ async fn permissions_checks_define_token_ns() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn permissions_checks_define_token_db() {
|
async fn permissions_checks_define_access_db() {
|
||||||
let scenario = HashMap::from([
|
let scenario = HashMap::from([
|
||||||
("prepare", ""),
|
("prepare", ""),
|
||||||
("test", "DEFINE TOKEN token ON DB TYPE HS512 VALUE 'secret'"),
|
("test", "DEFINE ACCESS access ON DB TYPE JWT ALGORITHM HS512 KEY 'secret'"),
|
||||||
("check", "INFO FOR DB"),
|
("check", "INFO FOR DB"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { token: \"DEFINE TOKEN token ON DATABASE TYPE HS512 VALUE 'secret'\" }, users: { } }"],
|
vec!["{ accesses: { access: \"DEFINE ACCESS access ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -1809,8 +1801,8 @@ async fn permissions_checks_define_user_ns() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ databases: { }, tokens: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
vec!["{ accesses: { }, databases: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||||
vec!["{ databases: { }, tokens: { }, users: { } }"]
|
vec!["{ accesses: { }, databases: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -1851,8 +1843,8 @@ async fn permissions_checks_define_user_db() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -1884,28 +1876,28 @@ async fn permissions_checks_define_user_db() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn permissions_checks_define_scope() {
|
async fn permissions_checks_define_access_record() {
|
||||||
let scenario = HashMap::from([
|
let scenario = HashMap::from([
|
||||||
("prepare", ""),
|
("prepare", ""),
|
||||||
("test", "DEFINE SCOPE account SESSION 1h;"),
|
("test", "DEFINE ACCESS account ON DATABASE TYPE RECORD DURATION 1h WITH JWT ALGORITHM HS512 KEY 'secret'"),
|
||||||
("check", "INFO FOR DB"),
|
("check", "INFO FOR DB"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { account: 'DEFINE SCOPE account SESSION 1h' }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { account: \"DEFINE ACCESS account ON DATABASE TYPE RECORD DURATION 1h WITH JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
// Root level
|
// Root level
|
||||||
((().into(), Role::Owner), ("NS", "DB"), true),
|
((().into(), Role::Owner), ("NS", "DB"), true),
|
||||||
((().into(), Role::Editor), ("NS", "DB"), true),
|
((().into(), Role::Editor), ("NS", "DB"), false),
|
||||||
((().into(), Role::Viewer), ("NS", "DB"), false),
|
((().into(), Role::Viewer), ("NS", "DB"), false),
|
||||||
// Namespace level
|
// Namespace level
|
||||||
((("NS",).into(), Role::Owner), ("NS", "DB"), true),
|
((("NS",).into(), Role::Owner), ("NS", "DB"), true),
|
||||||
((("NS",).into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
((("NS",).into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
||||||
((("NS",).into(), Role::Editor), ("NS", "DB"), true),
|
((("NS",).into(), Role::Editor), ("NS", "DB"), false),
|
||||||
((("NS",).into(), Role::Editor), ("OTHER_NS", "DB"), false),
|
((("NS",).into(), Role::Editor), ("OTHER_NS", "DB"), false),
|
||||||
((("NS",).into(), Role::Viewer), ("NS", "DB"), false),
|
((("NS",).into(), Role::Viewer), ("NS", "DB"), false),
|
||||||
((("NS",).into(), Role::Viewer), ("OTHER_NS", "DB"), false),
|
((("NS",).into(), Role::Viewer), ("OTHER_NS", "DB"), false),
|
||||||
|
@ -1913,7 +1905,7 @@ async fn permissions_checks_define_scope() {
|
||||||
((("NS", "DB").into(), Role::Owner), ("NS", "DB"), true),
|
((("NS", "DB").into(), Role::Owner), ("NS", "DB"), true),
|
||||||
((("NS", "DB").into(), Role::Owner), ("NS", "OTHER_DB"), false),
|
((("NS", "DB").into(), Role::Owner), ("NS", "OTHER_DB"), false),
|
||||||
((("NS", "DB").into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
((("NS", "DB").into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
||||||
((("NS", "DB").into(), Role::Editor), ("NS", "DB"), true),
|
((("NS", "DB").into(), Role::Editor), ("NS", "DB"), false),
|
||||||
((("NS", "DB").into(), Role::Editor), ("NS", "OTHER_DB"), false),
|
((("NS", "DB").into(), Role::Editor), ("NS", "OTHER_DB"), false),
|
||||||
((("NS", "DB").into(), Role::Editor), ("OTHER_NS", "DB"), false),
|
((("NS", "DB").into(), Role::Editor), ("OTHER_NS", "DB"), false),
|
||||||
((("NS", "DB").into(), Role::Viewer), ("NS", "DB"), false),
|
((("NS", "DB").into(), Role::Viewer), ("NS", "DB"), false),
|
||||||
|
@ -1935,8 +1927,8 @@ async fn permissions_checks_define_param() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo' PERMISSIONS FULL\" }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo' PERMISSIONS FULL\" }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -1974,8 +1966,8 @@ async fn permissions_checks_define_table() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB TYPE ANY SCHEMALESS PERMISSIONS NONE' }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { TB: 'DEFINE TABLE TB TYPE ANY SCHEMALESS PERMISSIONS NONE' }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -2159,17 +2151,16 @@ async fn define_statement_table_permissions() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: {
|
tables: {
|
||||||
default: 'DEFINE TABLE default TYPE ANY SCHEMALESS PERMISSIONS NONE',
|
default: 'DEFINE TABLE default TYPE ANY SCHEMALESS PERMISSIONS NONE',
|
||||||
full: 'DEFINE TABLE full TYPE ANY SCHEMALESS PERMISSIONS FULL',
|
full: 'DEFINE TABLE full TYPE ANY SCHEMALESS PERMISSIONS FULL',
|
||||||
select_full: 'DEFINE TABLE select_full TYPE ANY SCHEMALESS PERMISSIONS FOR select FULL, FOR create, update, delete NONE'
|
select_full: 'DEFINE TABLE select_full TYPE ANY SCHEMALESS PERMISSIONS FOR select FULL, FOR create, update, delete NONE'
|
||||||
},
|
},
|
||||||
tokens: {},
|
|
||||||
users: {}
|
users: {}
|
||||||
}",
|
}",
|
||||||
);
|
);
|
||||||
|
@ -2499,10 +2490,10 @@ async fn redefining_existing_param_with_if_not_exists_should_error() -> Result<(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn redefining_existing_scope_should_not_error() -> Result<(), Error> {
|
async fn redefining_existing_access_should_not_error() -> Result<(), Error> {
|
||||||
let sql = "
|
let sql = "
|
||||||
DEFINE SCOPE example;
|
DEFINE ACCESS example ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||||
DEFINE SCOPE example;
|
DEFINE ACCESS example ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||||
";
|
";
|
||||||
let dbs = new_ds().await?;
|
let dbs = new_ds().await?;
|
||||||
let ses = Session::owner().with_ns("test").with_db("test");
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
@ -2519,10 +2510,10 @@ async fn redefining_existing_scope_should_not_error() -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn redefining_existing_scope_with_if_not_exists_should_error() -> Result<(), Error> {
|
async fn redefining_existing_access_with_if_not_exists_should_error() -> Result<(), Error> {
|
||||||
let sql = "
|
let sql = "
|
||||||
DEFINE SCOPE IF NOT EXISTS example;
|
DEFINE ACCESS IF NOT EXISTS example ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||||
DEFINE SCOPE IF NOT EXISTS example;
|
DEFINE ACCESS IF NOT EXISTS example ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||||
";
|
";
|
||||||
let dbs = new_ds().await?;
|
let dbs = new_ds().await?;
|
||||||
let ses = Session::owner().with_ns("test").with_db("test");
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
@ -2533,7 +2524,7 @@ async fn redefining_existing_scope_with_if_not_exists_should_error() -> Result<(
|
||||||
assert_eq!(tmp, Value::None);
|
assert_eq!(tmp, Value::None);
|
||||||
//
|
//
|
||||||
let tmp = res.remove(0).result.unwrap_err();
|
let tmp = res.remove(0).result.unwrap_err();
|
||||||
assert!(matches!(tmp, Error::ScAlreadyExists { .. }),);
|
assert!(matches!(tmp, Error::AccessDbAlreadyExists { .. }),);
|
||||||
//
|
//
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2578,46 +2569,6 @@ async fn redefining_existing_table_with_if_not_exists_should_error() -> Result<(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn redefining_existing_token_should_not_error() -> Result<(), Error> {
|
|
||||||
let sql = "
|
|
||||||
DEFINE TOKEN example ON SCOPE example TYPE HS512 VALUE \"example\";
|
|
||||||
DEFINE TOKEN example ON SCOPE example TYPE HS512 VALUE \"example\";
|
|
||||||
";
|
|
||||||
let dbs = new_ds().await?;
|
|
||||||
let ses = Session::owner().with_ns("test").with_db("test");
|
|
||||||
let res = &mut dbs.execute(sql, &ses, None).await?;
|
|
||||||
assert_eq!(res.len(), 2);
|
|
||||||
//
|
|
||||||
let tmp = res.remove(0).result?;
|
|
||||||
assert_eq!(tmp, Value::None);
|
|
||||||
//
|
|
||||||
let tmp = res.remove(0).result?;
|
|
||||||
assert_eq!(tmp, Value::None);
|
|
||||||
//
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn redefining_existing_token_with_if_not_exists_should_error() -> Result<(), Error> {
|
|
||||||
let sql = "
|
|
||||||
DEFINE TOKEN IF NOT EXISTS example ON SCOPE example TYPE HS512 VALUE \"example\";
|
|
||||||
DEFINE TOKEN IF NOT EXISTS example ON SCOPE example TYPE HS512 VALUE \"example\";
|
|
||||||
";
|
|
||||||
let dbs = new_ds().await?;
|
|
||||||
let ses = Session::owner().with_ns("test").with_db("test");
|
|
||||||
let res = &mut dbs.execute(sql, &ses, None).await?;
|
|
||||||
assert_eq!(res.len(), 2);
|
|
||||||
//
|
|
||||||
let tmp = res.remove(0).result?;
|
|
||||||
assert_eq!(tmp, Value::None);
|
|
||||||
//
|
|
||||||
let tmp = res.remove(0).result.unwrap_err();
|
|
||||||
assert!(matches!(tmp, Error::StAlreadyExists { .. }),);
|
|
||||||
//
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn redefining_existing_user_should_not_error() -> Result<(), Error> {
|
async fn redefining_existing_user_should_not_error() -> Result<(), Error> {
|
||||||
let sql = "
|
let sql = "
|
||||||
|
@ -2831,12 +2782,11 @@ async fn define_table_relation_redefinition_info() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: { likes: 'DEFINE TABLE likes TYPE RELATION IN person OUT person SCHEMALESS PERMISSIONS NONE' },
|
tables: { likes: 'DEFINE TABLE likes TYPE RELATION IN person OUT person SCHEMALESS PERMISSIONS NONE' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
@ -2861,12 +2811,11 @@ async fn define_table_relation_redefinition_info() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: { likes: 'DEFINE TABLE likes TYPE RELATION IN person OUT person | thing SCHEMALESS PERMISSIONS NONE' },
|
tables: { likes: 'DEFINE TABLE likes TYPE RELATION IN person OUT person | thing SCHEMALESS PERMISSIONS NONE' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
@ -2891,12 +2840,11 @@ async fn define_table_relation_redefinition_info() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: { likes: 'DEFINE TABLE likes TYPE RELATION IN person OUT person | thing | other SCHEMALESS PERMISSIONS NONE' },
|
tables: { likes: 'DEFINE TABLE likes TYPE RELATION IN person OUT person | thing | other SCHEMALESS PERMISSIONS NONE' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
|
|
@ -456,7 +456,7 @@ async fn delete_with_permissions() -> Result<(), Error> {
|
||||||
DELETE friends_with:1 RETURN BEFORE;
|
DELETE friends_with:1 RETURN BEFORE;
|
||||||
DELETE friends_with:2 RETURN BEFORE;
|
DELETE friends_with:2 RETURN BEFORE;
|
||||||
";
|
";
|
||||||
let ses = Session::for_scope("test", "test", "test", Thing::from(("user", "john")).into());
|
let ses = Session::for_record("test", "test", "test", Thing::from(("user", "john")).into());
|
||||||
let res = &mut dbs.execute(sql, &ses, None).await?;
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
assert_eq!(res.len(), 2);
|
assert_eq!(res.len(), 2);
|
||||||
//
|
//
|
||||||
|
|
|
@ -842,7 +842,7 @@ async fn field_definition_edge_permissions() -> Result<(), Error> {
|
||||||
RELATE business:one->contact:one->business:two;
|
RELATE business:one->contact:one->business:two;
|
||||||
RELATE business:two->contact:two->business:one;
|
RELATE business:two->contact:two->business:one;
|
||||||
";
|
";
|
||||||
let ses = Session::for_scope("test", "test", "test", Thing::from(("user", "one")).into());
|
let ses = Session::for_record("test", "test", "test", Thing::from(("user", "one")).into());
|
||||||
let res = &mut dbs.execute(sql, &ses, None).await?;
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
assert_eq!(res.len(), 2);
|
assert_eq!(res.len(), 2);
|
||||||
//
|
//
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub async fn iam_run_case(
|
||||||
sess: &Session,
|
sess: &Session,
|
||||||
should_succeed: bool,
|
should_succeed: bool,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Use the same scope as the test statement, but change the Auth to run the check with full permissions
|
// Use the session as the test statement, but change the Auth to run the check with full permissions
|
||||||
let mut owner_sess = sess.clone();
|
let mut owner_sess = sess.clone();
|
||||||
owner_sess.au = Arc::new(Auth::for_root(Role::Owner));
|
owner_sess.au = Arc::new(Auth::for_root(Role::Owner));
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ async fn info_for_ns() {
|
||||||
let sql = r#"
|
let sql = r#"
|
||||||
DEFINE DATABASE DB;
|
DEFINE DATABASE DB;
|
||||||
DEFINE USER user ON NS PASSWORD 'pass';
|
DEFINE USER user ON NS PASSWORD 'pass';
|
||||||
DEFINE TOKEN token ON NS TYPE HS512 VALUE 'secret';
|
DEFINE ACCESS access ON NS TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||||
INFO FOR NS
|
INFO FOR NS
|
||||||
"#;
|
"#;
|
||||||
let dbs = new_ds().await.unwrap();
|
let dbs = new_ds().await.unwrap();
|
||||||
|
@ -52,7 +52,7 @@ async fn info_for_ns() {
|
||||||
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
||||||
|
|
||||||
let output_regex = Regex::new(
|
let output_regex = Regex::new(
|
||||||
r"\{ databases: \{ DB: .* \}, tokens: \{ token: .* \}, users: \{ user: .* \} \}",
|
r"\{ accesses: \{ access: .* \}, databases: \{ DB: .* \}, users: \{ user: .* \} \}",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let out_str = out.unwrap().to_string();
|
let out_str = out.unwrap().to_string();
|
||||||
|
@ -68,9 +68,9 @@ async fn info_for_ns() {
|
||||||
async fn info_for_db() {
|
async fn info_for_db() {
|
||||||
let sql = r#"
|
let sql = r#"
|
||||||
DEFINE TABLE TB;
|
DEFINE TABLE TB;
|
||||||
DEFINE SCOPE account SESSION 24h;
|
DEFINE ACCESS jwt ON DB TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||||
|
DEFINE ACCESS record ON DB TYPE RECORD DURATION 24h;
|
||||||
DEFINE USER user ON DB PASSWORD 'pass';
|
DEFINE USER user ON DB PASSWORD 'pass';
|
||||||
DEFINE TOKEN token ON DB TYPE HS512 VALUE 'secret';
|
|
||||||
DEFINE FUNCTION fn::greet() {RETURN "Hello";};
|
DEFINE FUNCTION fn::greet() {RETURN "Hello";};
|
||||||
DEFINE PARAM $param VALUE "foo";
|
DEFINE PARAM $param VALUE "foo";
|
||||||
DEFINE ANALYZER analyzer TOKENIZERS BLANK;
|
DEFINE ANALYZER analyzer TOKENIZERS BLANK;
|
||||||
|
@ -85,33 +85,7 @@ async fn info_for_db() {
|
||||||
let out = res.pop().unwrap().output();
|
let out = res.pop().unwrap().output();
|
||||||
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
||||||
|
|
||||||
let output_regex = Regex::new(r"\{ analyzers: \{ analyzer: .* \}, functions: \{ greet: .* \}, params: \{ param: .* \}, scopes: \{ account: .* \}, tables: \{ TB: .* \}, tokens: \{ token: .* \}, users: \{ user: .* \} \}").unwrap();
|
let output_regex = Regex::new(r"\{ accesses: \{ jwt: .*, record: .* \}, analyzers: \{ analyzer: .* \}, functions: \{ greet: .* \}, params: \{ param: .* \}, tables: \{ TB: .* \}, users: \{ user: .* \} \}").unwrap();
|
||||||
let out_str = out.unwrap().to_string();
|
|
||||||
assert!(
|
|
||||||
output_regex.is_match(&out_str),
|
|
||||||
"Output '{}' doesn't match regex '{}'",
|
|
||||||
out_str,
|
|
||||||
output_regex
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn info_for_scope() {
|
|
||||||
let sql = r#"
|
|
||||||
DEFINE SCOPE account SESSION 24h;
|
|
||||||
DEFINE TOKEN token ON SCOPE account TYPE HS512 VALUE 'secret';
|
|
||||||
INFO FOR SCOPE account;
|
|
||||||
"#;
|
|
||||||
let dbs = new_ds().await.unwrap();
|
|
||||||
let ses = Session::owner().with_ns("ns").with_db("db");
|
|
||||||
|
|
||||||
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
|
|
||||||
assert_eq!(res.len(), 3);
|
|
||||||
|
|
||||||
let out = res.pop().unwrap().output();
|
|
||||||
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
|
||||||
|
|
||||||
let output_regex = Regex::new(r"\{ tokens: \{ token: .* \} \}").unwrap();
|
|
||||||
let out_str = out.unwrap().to_string();
|
let out_str = out.unwrap().to_string();
|
||||||
assert!(
|
assert!(
|
||||||
output_regex.is_match(&out_str),
|
output_regex.is_match(&out_str),
|
||||||
|
@ -273,8 +247,8 @@ async fn permissions_checks_info_ns() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -312,8 +286,8 @@ async fn permissions_checks_info_db() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -344,45 +318,6 @@ async fn permissions_checks_info_db() {
|
||||||
assert!(res.is_ok(), "{}", res.unwrap_err());
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn permissions_checks_info_scope() {
|
|
||||||
let scenario = HashMap::from([
|
|
||||||
("prepare", "DEFINE SCOPE scope SESSION 1h"),
|
|
||||||
("test", "INFO FOR SCOPE scope"),
|
|
||||||
("check", "INFO FOR SCOPE scope"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
|
||||||
let check_results = [vec!["{ tokens: { } }"], vec!["{ tokens: { } }"]];
|
|
||||||
|
|
||||||
let test_cases = [
|
|
||||||
// Root level
|
|
||||||
((().into(), Role::Owner), ("NS", "DB"), true),
|
|
||||||
((().into(), Role::Editor), ("NS", "DB"), true),
|
|
||||||
((().into(), Role::Viewer), ("NS", "DB"), true),
|
|
||||||
// Namespace level
|
|
||||||
((("NS",).into(), Role::Owner), ("NS", "DB"), true),
|
|
||||||
((("NS",).into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
|
||||||
((("NS",).into(), Role::Editor), ("NS", "DB"), true),
|
|
||||||
((("NS",).into(), Role::Editor), ("OTHER_NS", "DB"), false),
|
|
||||||
((("NS",).into(), Role::Viewer), ("NS", "DB"), true),
|
|
||||||
((("NS",).into(), Role::Viewer), ("OTHER_NS", "DB"), false),
|
|
||||||
// Database level
|
|
||||||
((("NS", "DB").into(), Role::Owner), ("NS", "DB"), true),
|
|
||||||
((("NS", "DB").into(), Role::Owner), ("NS", "OTHER_DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Editor), ("NS", "DB"), true),
|
|
||||||
((("NS", "DB").into(), Role::Editor), ("NS", "OTHER_DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Editor), ("OTHER_NS", "DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Viewer), ("NS", "DB"), true),
|
|
||||||
((("NS", "DB").into(), Role::Viewer), ("NS", "OTHER_DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Viewer), ("OTHER_NS", "DB"), false),
|
|
||||||
];
|
|
||||||
|
|
||||||
let res = iam_check_cases(test_cases.iter(), &scenario, check_results).await;
|
|
||||||
assert!(res.is_ok(), "{}", res.unwrap_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn permissions_checks_info_table() {
|
async fn permissions_checks_info_table() {
|
||||||
let scenario = HashMap::from([
|
let scenario = HashMap::from([
|
||||||
|
|
|
@ -26,12 +26,11 @@ async fn define_global_param() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: { test: 'DEFINE PARAM $test VALUE 12345 PERMISSIONS FULL' },
|
params: { test: 'DEFINE PARAM $test VALUE 12345 PERMISSIONS FULL' },
|
||||||
scopes: {},
|
|
||||||
tables: {},
|
tables: {},
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
|
|
@ -36,12 +36,11 @@ async fn remove_statement_table() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: {},
|
tables: {},
|
||||||
users: {}
|
users: {}
|
||||||
}",
|
}",
|
||||||
|
@ -71,12 +70,11 @@ async fn remove_statement_analyzer() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: {},
|
tables: {},
|
||||||
users: {}
|
users: {}
|
||||||
}",
|
}",
|
||||||
|
@ -418,9 +416,9 @@ async fn should_not_error_when_remove_param_if_exists() -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn should_error_when_remove_and_scope_does_not_exist() -> Result<(), Error> {
|
async fn should_error_when_remove_and_access_does_not_exist() -> Result<(), Error> {
|
||||||
let sql = "
|
let sql = "
|
||||||
REMOVE SCOPE foo;
|
REMOVE ACCESS foo ON DB;
|
||||||
";
|
";
|
||||||
let dbs = new_ds().await?;
|
let dbs = new_ds().await?;
|
||||||
let ses = Session::owner().with_ns("test").with_db("test");
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
@ -428,47 +426,15 @@ async fn should_error_when_remove_and_scope_does_not_exist() -> Result<(), Error
|
||||||
assert_eq!(res.len(), 1);
|
assert_eq!(res.len(), 1);
|
||||||
//
|
//
|
||||||
let tmp = res.remove(0).result.unwrap_err();
|
let tmp = res.remove(0).result.unwrap_err();
|
||||||
assert!(matches!(tmp, Error::ScNotFound { .. }),);
|
assert!(matches!(tmp, Error::DaNotFound { .. }),);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn should_not_error_when_remove_scope_if_exists() -> Result<(), Error> {
|
async fn should_not_error_when_remove_access_if_exists() -> Result<(), Error> {
|
||||||
let sql = "
|
let sql = "
|
||||||
REMOVE SCOPE IF EXISTS foo;
|
REMOVE ACCESS IF EXISTS foo ON DB;
|
||||||
";
|
|
||||||
let dbs = new_ds().await?;
|
|
||||||
let ses = Session::owner().with_ns("test").with_db("test");
|
|
||||||
let res = &mut dbs.execute(sql, &ses, None).await?;
|
|
||||||
assert_eq!(res.len(), 1);
|
|
||||||
//
|
|
||||||
let tmp = res.remove(0).result?;
|
|
||||||
assert_eq!(tmp, Value::None);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn should_error_when_remove_and_token_does_not_exist() -> Result<(), Error> {
|
|
||||||
let sql = "
|
|
||||||
REMOVE TOKEN foo ON NAMESPACE;
|
|
||||||
";
|
|
||||||
let dbs = new_ds().await?;
|
|
||||||
let ses = Session::owner().with_ns("test").with_db("test");
|
|
||||||
let res = &mut dbs.execute(sql, &ses, None).await?;
|
|
||||||
assert_eq!(res.len(), 1);
|
|
||||||
//
|
|
||||||
let tmp = res.remove(0).result.unwrap_err();
|
|
||||||
assert!(matches!(tmp, Error::NtNotFound { .. }),);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn should_not_error_when_remove_token_if_exists() -> Result<(), Error> {
|
|
||||||
let sql = "
|
|
||||||
REMOVE TOKEN IF EXISTS foo ON NAMESPACE;
|
|
||||||
";
|
";
|
||||||
let dbs = new_ds().await?;
|
let dbs = new_ds().await?;
|
||||||
let ses = Session::owner().with_ns("test").with_db("test");
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
@ -569,8 +535,8 @@ async fn permissions_checks_remove_db() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||||
vec!["{ databases: { DB: 'DEFINE DATABASE DB' }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, databases: { DB: 'DEFINE DATABASE DB' }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -611,8 +577,8 @@ async fn permissions_checks_remove_function() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; } PERMISSIONS FULL\" }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; } PERMISSIONS FULL\" }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -653,8 +619,8 @@ async fn permissions_checks_remove_analyzer() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -686,17 +652,17 @@ async fn permissions_checks_remove_analyzer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn permissions_checks_remove_ns_token() {
|
async fn permissions_checks_remove_ns_access() {
|
||||||
let scenario = HashMap::from([
|
let scenario = HashMap::from([
|
||||||
("prepare", "DEFINE TOKEN token ON NS TYPE HS512 VALUE 'secret'"),
|
("prepare", "DEFINE ACCESS access ON NS TYPE JWT ALGORITHM HS512 KEY 'secret'"),
|
||||||
("test", "REMOVE TOKEN token ON NS"),
|
("test", "REMOVE ACCESS access ON NS"),
|
||||||
("check", "INFO FOR NS"),
|
("check", "INFO FOR NS"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||||
vec!["{ databases: { }, tokens: { token: \"DEFINE TOKEN token ON NAMESPACE TYPE HS512 VALUE 'secret'\" }, users: { } }"],
|
vec!["{ accesses: { access: \"DEFINE ACCESS access ON NAMESPACE TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, databases: { }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -728,17 +694,17 @@ async fn permissions_checks_remove_ns_token() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn permissions_checks_remove_db_token() {
|
async fn permissions_checks_remove_db_access() {
|
||||||
let scenario = HashMap::from([
|
let scenario = HashMap::from([
|
||||||
("prepare", "DEFINE TOKEN token ON DB TYPE HS512 VALUE 'secret'"),
|
("prepare", "DEFINE ACCESS access ON DB TYPE JWT ALGORITHM HS512 KEY 'secret'"),
|
||||||
("test", "REMOVE TOKEN token ON DB"),
|
("test", "REMOVE ACCESS access ON DB"),
|
||||||
("check", "INFO FOR DB"),
|
("check", "INFO FOR DB"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { token: \"DEFINE TOKEN token ON DATABASE TYPE HS512 VALUE 'secret'\" }, users: { } }"],
|
vec!["{ accesses: { access: \"DEFINE ACCESS access ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -821,8 +787,8 @@ async fn permissions_checks_remove_ns_user() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||||
vec!["{ databases: { }, tokens: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
vec!["{ accesses: { }, databases: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -863,8 +829,8 @@ async fn permissions_checks_remove_db_user() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -895,48 +861,6 @@ async fn permissions_checks_remove_db_user() {
|
||||||
assert!(res.is_ok(), "{}", res.unwrap_err());
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn permissions_checks_remove_scope() {
|
|
||||||
let scenario = HashMap::from([
|
|
||||||
("prepare", "DEFINE SCOPE account SESSION 1h;"),
|
|
||||||
("test", "REMOVE SCOPE account"),
|
|
||||||
("check", "INFO FOR DB"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
|
||||||
let check_results = [
|
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { account: 'DEFINE SCOPE account SESSION 1h' }, tables: { }, tokens: { }, users: { } }"],
|
|
||||||
];
|
|
||||||
|
|
||||||
let test_cases = [
|
|
||||||
// Root level
|
|
||||||
((().into(), Role::Owner), ("NS", "DB"), true),
|
|
||||||
((().into(), Role::Editor), ("NS", "DB"), true),
|
|
||||||
((().into(), Role::Viewer), ("NS", "DB"), false),
|
|
||||||
// Namespace level
|
|
||||||
((("NS",).into(), Role::Owner), ("NS", "DB"), true),
|
|
||||||
((("NS",).into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
|
||||||
((("NS",).into(), Role::Editor), ("NS", "DB"), true),
|
|
||||||
((("NS",).into(), Role::Editor), ("OTHER_NS", "DB"), false),
|
|
||||||
((("NS",).into(), Role::Viewer), ("NS", "DB"), false),
|
|
||||||
((("NS",).into(), Role::Viewer), ("OTHER_NS", "DB"), false),
|
|
||||||
// Database level
|
|
||||||
((("NS", "DB").into(), Role::Owner), ("NS", "DB"), true),
|
|
||||||
((("NS", "DB").into(), Role::Owner), ("NS", "OTHER_DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Editor), ("NS", "DB"), true),
|
|
||||||
((("NS", "DB").into(), Role::Editor), ("NS", "OTHER_DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Editor), ("OTHER_NS", "DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Viewer), ("NS", "DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Viewer), ("NS", "OTHER_DB"), false),
|
|
||||||
((("NS", "DB").into(), Role::Viewer), ("OTHER_NS", "DB"), false),
|
|
||||||
];
|
|
||||||
|
|
||||||
let res = iam_check_cases(test_cases.iter(), &scenario, check_results).await;
|
|
||||||
assert!(res.is_ok(), "{}", res.unwrap_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn permissions_checks_remove_param() {
|
async fn permissions_checks_remove_param() {
|
||||||
let scenario = HashMap::from([
|
let scenario = HashMap::from([
|
||||||
|
@ -947,8 +871,8 @@ async fn permissions_checks_remove_param() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo' PERMISSIONS FULL\" }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo' PERMISSIONS FULL\" }, tables: { }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -989,8 +913,8 @@ async fn permissions_checks_remove_table() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB TYPE ANY SCHEMALESS PERMISSIONS NONE' }, tokens: { }, users: { } }"],
|
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { TB: 'DEFINE TABLE TB TYPE ANY SCHEMALESS PERMISSIONS NONE' }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
|
|
@ -242,8 +242,8 @@ async fn loose_mode_all_ok() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
databases: { test: 'DEFINE DATABASE test' },
|
databases: { test: 'DEFINE DATABASE test' },
|
||||||
tokens: {},
|
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
);
|
);
|
||||||
|
@ -252,12 +252,11 @@ async fn loose_mode_all_ok() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
analyzers: {},
|
analyzers: {},
|
||||||
tokens: {},
|
|
||||||
functions: {},
|
functions: {},
|
||||||
models: {},
|
models: {},
|
||||||
params: {},
|
params: {},
|
||||||
scopes: {},
|
|
||||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE' },
|
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
|
|
@ -41,7 +41,7 @@ struct SigninParams<'a> {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
db: Option<&'a str>,
|
db: Option<&'a str>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
sc: Option<&'a str>,
|
ac: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SocketMsg {
|
enum SocketMsg {
|
||||||
|
@ -385,7 +385,7 @@ impl Socket {
|
||||||
pass: &str,
|
pass: &str,
|
||||||
ns: Option<&str>,
|
ns: Option<&str>,
|
||||||
db: Option<&str>,
|
db: Option<&str>,
|
||||||
sc: Option<&str>,
|
ac: Option<&str>,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
// Send message and receive response
|
// Send message and receive response
|
||||||
let msg = self
|
let msg = self
|
||||||
|
@ -396,7 +396,7 @@ impl Socket {
|
||||||
pass,
|
pass,
|
||||||
ns,
|
ns,
|
||||||
db,
|
db,
|
||||||
sc
|
ac
|
||||||
}]),
|
}]),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -35,11 +35,11 @@ async fn info() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
socket.send_message_use(Some(NS), Some(DB)).await?;
|
socket.send_message_use(Some(NS), Some(DB)).await?;
|
||||||
// Define a user table
|
// Define a user table
|
||||||
socket.send_message_query("DEFINE TABLE user PERMISSIONS FULL").await?;
|
socket.send_message_query("DEFINE TABLE user PERMISSIONS FULL").await?;
|
||||||
// Define a user scope
|
// Define a user record access method
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE scope SESSION 24h
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 24h
|
||||||
SIGNUP ( CREATE user SET user = $user, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET user = $user, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE user = $user AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE user = $user AND crypto::argon2::compare(pass, $pass) )
|
||||||
;
|
;
|
||||||
|
@ -57,8 +57,8 @@ async fn info() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
// Sign in as scope user
|
// Sign in as record user
|
||||||
socket.send_message_signin("user", "pass", Some(NS), Some(DB), Some("scope")).await?;
|
socket.send_message_signin("user", "pass", Some(NS), Some(DB), Some("user")).await?;
|
||||||
// Send INFO command
|
// Send INFO command
|
||||||
let res = socket.send_request("info", json!([])).await?;
|
let res = socket.send_request("info", json!([])).await?;
|
||||||
assert!(res["result"].is_object(), "result: {:?}", res);
|
assert!(res["result"].is_object(), "result: {:?}", res);
|
||||||
|
@ -79,11 +79,11 @@ async fn signup() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
socket.send_message_signin(USER, PASS, None, None, None).await?;
|
socket.send_message_signin(USER, PASS, None, None, None).await?;
|
||||||
// Specify a namespace and database
|
// Specify a namespace and database
|
||||||
socket.send_message_use(Some(NS), Some(DB)).await?;
|
socket.send_message_use(Some(NS), Some(DB)).await?;
|
||||||
// Setup the scope
|
// Define a user record access method
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE scope SESSION 24h
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 24h
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
;"#,
|
;"#,
|
||||||
|
@ -96,7 +96,7 @@ async fn signup() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
json!([{
|
json!([{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]),
|
}]),
|
||||||
|
@ -127,11 +127,11 @@ async fn signin() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
socket.send_message_signin(USER, PASS, None, None, None).await?;
|
socket.send_message_signin(USER, PASS, None, None, None).await?;
|
||||||
// Specify a namespace and database
|
// Specify a namespace and database
|
||||||
socket.send_message_use(Some(NS), Some(DB)).await?;
|
socket.send_message_use(Some(NS), Some(DB)).await?;
|
||||||
// Setup the scope
|
// Define a user record access method
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE scope SESSION 24h
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 24h
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
;"#,
|
;"#,
|
||||||
|
@ -145,7 +145,7 @@ async fn signin() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
[{
|
[{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]
|
}]
|
||||||
|
@ -170,7 +170,7 @@ async fn signin() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
[{
|
[{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]),
|
}]),
|
||||||
|
@ -868,11 +868,11 @@ async fn variable_auth_live_query() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
socket_permanent.send_message_signin(USER, PASS, None, None, None).await?;
|
socket_permanent.send_message_signin(USER, PASS, None, None, None).await?;
|
||||||
// Specify a namespace and database
|
// Specify a namespace and database
|
||||||
socket_permanent.send_message_use(Some(NS), Some(DB)).await?;
|
socket_permanent.send_message_use(Some(NS), Some(DB)).await?;
|
||||||
// Setup the scope
|
// Define a user record access method
|
||||||
socket_permanent
|
socket_permanent
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE scope SESSION 1s
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1s
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
;"#,
|
;"#,
|
||||||
|
@ -887,7 +887,7 @@ async fn variable_auth_live_query() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
json!([{
|
json!([{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]),
|
}]),
|
||||||
|
@ -933,23 +933,23 @@ async fn session_expiration() {
|
||||||
socket.send_message_signin(USER, PASS, None, None, None).await.unwrap();
|
socket.send_message_signin(USER, PASS, None, None, None).await.unwrap();
|
||||||
// Specify a namespace and database
|
// Specify a namespace and database
|
||||||
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
||||||
// Setup the scope
|
// Define a user record access method
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE scope SESSION 1s
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1s
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
;"#,
|
;"#,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Create resource that requires a scope session to query
|
// Create resource that requires a session with the access method to query
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE TABLE test SCHEMALESS
|
DEFINE TABLE test SCHEMALESS
|
||||||
PERMISSIONS FOR select, create, update, delete WHERE $scope = "scope"
|
PERMISSIONS FOR select, create, update, delete WHERE $access = "user"
|
||||||
;"#,
|
;"#,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -970,7 +970,7 @@ async fn session_expiration() {
|
||||||
[{
|
[{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]
|
}]
|
||||||
|
@ -1012,7 +1012,7 @@ async fn session_expiration() {
|
||||||
[{
|
[{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]
|
}]
|
||||||
|
@ -1043,23 +1043,23 @@ async fn session_expiration_operations() {
|
||||||
let root_token = socket.send_message_signin(USER, PASS, None, None, None).await.unwrap();
|
let root_token = socket.send_message_signin(USER, PASS, None, None, None).await.unwrap();
|
||||||
// Specify a namespace and database
|
// Specify a namespace and database
|
||||||
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
||||||
// Setup the scope
|
// Define a user record access method
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE scope SESSION 1s
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1s
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
;"#,
|
;"#,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Create resource that requires a scope session to query
|
// Create resource that requires a session with the access method to query
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE TABLE test SCHEMALESS
|
DEFINE TABLE test SCHEMALESS
|
||||||
PERMISSIONS FOR select, create, update, delete WHERE $scope = "scope"
|
PERMISSIONS FOR select, create, update, delete WHERE $access = "user"
|
||||||
;"#,
|
;"#,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -1080,7 +1080,7 @@ async fn session_expiration_operations() {
|
||||||
[{
|
[{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]
|
}]
|
||||||
|
@ -1217,7 +1217,7 @@ async fn session_expiration_operations() {
|
||||||
json!([{
|
json!([{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "another@email.com",
|
"email": "another@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]),
|
}]),
|
||||||
|
@ -1248,7 +1248,7 @@ async fn session_expiration_operations() {
|
||||||
[{
|
[{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "another@email.com",
|
"email": "another@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]
|
}]
|
||||||
|
@ -1299,23 +1299,23 @@ async fn session_reauthentication() {
|
||||||
socket.send_message_query("INFO FOR ROOT").await.unwrap();
|
socket.send_message_query("INFO FOR ROOT").await.unwrap();
|
||||||
// Specify a namespace and database
|
// Specify a namespace and database
|
||||||
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
||||||
// Setup the scope
|
// Define a user record access method
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE scope SESSION 1h
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1h
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
;"#,
|
;"#,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Create resource that requires a scope session to query
|
// Create resource that requires a session with the access method to query
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE TABLE test SCHEMALESS
|
DEFINE TABLE test SCHEMALESS
|
||||||
PERMISSIONS FOR select, create, update, delete WHERE $scope = "scope"
|
PERMISSIONS FOR select, create, update, delete WHERE $access = "user"
|
||||||
;"#,
|
;"#,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -1336,7 +1336,7 @@ async fn session_reauthentication() {
|
||||||
[{
|
[{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]
|
}]
|
||||||
|
@ -1353,7 +1353,7 @@ async fn session_reauthentication() {
|
||||||
assert!(res["result"].is_string(), "result: {:?}", res);
|
assert!(res["result"].is_string(), "result: {:?}", res);
|
||||||
let res = res["result"].as_str().unwrap();
|
let res = res["result"].as_str().unwrap();
|
||||||
assert!(res.starts_with("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9"), "result: {}", res);
|
assert!(res.starts_with("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9"), "result: {}", res);
|
||||||
// Authenticate using the scope token
|
// Authenticate using the token
|
||||||
socket.send_request("authenticate", json!([res,])).await.unwrap();
|
socket.send_request("authenticate", json!([res,])).await.unwrap();
|
||||||
// Check that we do not have root access
|
// Check that we do not have root access
|
||||||
let res = socket.send_message_query("INFO FOR ROOT").await.unwrap();
|
let res = socket.send_message_query("INFO FOR ROOT").await.unwrap();
|
||||||
|
@ -1363,7 +1363,7 @@ async fn session_reauthentication() {
|
||||||
"result: {:?}",
|
"result: {:?}",
|
||||||
res
|
res
|
||||||
);
|
);
|
||||||
// Check if the session is authenticated for the scope
|
// Check if the session is authenticated
|
||||||
let res = socket.send_message_query("SELECT VALUE working FROM test:1").await.unwrap();
|
let res = socket.send_message_query("SELECT VALUE working FROM test:1").await.unwrap();
|
||||||
assert_eq!(res[0]["result"], json!(["yes"]), "result: {:?}", res);
|
assert_eq!(res[0]["result"], json!(["yes"]), "result: {:?}", res);
|
||||||
// Authenticate using the root token
|
// Authenticate using the root token
|
||||||
|
@ -1387,23 +1387,23 @@ async fn session_reauthentication_expired() {
|
||||||
socket.send_message_query("INFO FOR ROOT").await.unwrap();
|
socket.send_message_query("INFO FOR ROOT").await.unwrap();
|
||||||
// Specify a namespace and database
|
// Specify a namespace and database
|
||||||
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
||||||
// Setup the scope
|
// Define a user record access method
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE scope SESSION 1s
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1s
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
;"#,
|
;"#,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Create resource that requires a scope session to query
|
// Create resource that requires a session with the access method to query
|
||||||
socket
|
socket
|
||||||
.send_message_query(
|
.send_message_query(
|
||||||
r#"
|
r#"
|
||||||
DEFINE TABLE test SCHEMALESS
|
DEFINE TABLE test SCHEMALESS
|
||||||
PERMISSIONS FOR select, create, update, delete WHERE $scope = "scope"
|
PERMISSIONS FOR select, create, update, delete WHERE $access = "user"
|
||||||
;"#,
|
;"#,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -1424,7 +1424,7 @@ async fn session_reauthentication_expired() {
|
||||||
[{
|
[{
|
||||||
"ns": NS,
|
"ns": NS,
|
||||||
"db": DB,
|
"db": DB,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
}]
|
}]
|
||||||
|
@ -1441,7 +1441,7 @@ async fn session_reauthentication_expired() {
|
||||||
assert!(res["result"].is_string(), "result: {:?}", res);
|
assert!(res["result"].is_string(), "result: {:?}", res);
|
||||||
let res = res["result"].as_str().unwrap();
|
let res = res["result"].as_str().unwrap();
|
||||||
assert!(res.starts_with("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9"), "result: {}", res);
|
assert!(res.starts_with("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9"), "result: {}", res);
|
||||||
// Authenticate using the scope token, which will expire soon
|
// Authenticate using the token, which will expire soon
|
||||||
socket.send_request("authenticate", json!([res,])).await.unwrap();
|
socket.send_request("authenticate", json!([res,])).await.unwrap();
|
||||||
// Wait two seconds for token to expire
|
// Wait two seconds for token to expire
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||||
|
|
|
@ -1160,14 +1160,14 @@ mod http_integration {
|
||||||
.default_headers(headers)
|
.default_headers(headers)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
// Create a scope
|
// Define a record access method
|
||||||
{
|
{
|
||||||
let res = client
|
let res = client
|
||||||
.post(format!("http://{addr}/sql"))
|
.post(format!("http://{addr}/sql"))
|
||||||
.basic_auth(USER, Some(PASS))
|
.basic_auth(USER, Some(PASS))
|
||||||
.body(
|
.body(
|
||||||
r#"
|
r#"
|
||||||
DEFINE SCOPE scope SESSION 24h
|
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 24h
|
||||||
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
|
||||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||||
;
|
;
|
||||||
|
@ -1178,13 +1178,13 @@ mod http_integration {
|
||||||
assert!(res.status().is_success(), "body: {}", res.text().await?);
|
assert!(res.status().is_success(), "body: {}", res.text().await?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signup into the scope
|
// Signup using the defined record access method
|
||||||
{
|
{
|
||||||
let req_body = serde_json::to_string(
|
let req_body = serde_json::to_string(
|
||||||
json!({
|
json!({
|
||||||
"ns": ns,
|
"ns": ns,
|
||||||
"db": db,
|
"db": db,
|
||||||
"sc": "scope",
|
"ac": "user",
|
||||||
"email": "email@email.com",
|
"email": "email@email.com",
|
||||||
"pass": "pass",
|
"pass": "pass",
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue