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);
|
||||
|
||||
/// 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.
|
||||
pub const ID_CHARS: [char; 36] = [
|
||||
|
@ -35,9 +35,9 @@ pub const SERVER_NAME: &str = "SurrealDB";
|
|||
/// Datastore processor batch size for scan operations
|
||||
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.
|
||||
pub static INSECURE_FORWARD_SCOPE_ERRORS: Lazy<bool> =
|
||||
lazy_env_parse!("SURREAL_INSECURE_FORWARD_SCOPE_ERRORS", bool, false);
|
||||
/// Forward all signup/signin query errors to a client performing record access. Do not use in production.
|
||||
pub static INSECURE_FORWARD_RECORD_ACCESS_ERRORS: Lazy<bool> =
|
||||
lazy_env_parse!("SURREAL_INSECURE_FORWARD_RECORD_ACCESS_ERRORS", bool, false);
|
||||
|
||||
#[cfg(any(
|
||||
feature = "kv-surrealkv",
|
||||
|
|
|
@ -405,9 +405,10 @@ impl Options {
|
|||
self.valid_for_db()?;
|
||||
res.on_db(self.ns(), self.db())
|
||||
}
|
||||
Base::Sc(sc) => {
|
||||
self.valid_for_db()?;
|
||||
res.on_scope(self.ns(), self.db(), sc)
|
||||
// TODO(gguillemas): This variant is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||
Base::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>,
|
||||
/// The currently selected database
|
||||
pub db: Option<String>,
|
||||
/// The currently selected authentication scope
|
||||
pub sc: Option<String>,
|
||||
/// The current scope authentication token
|
||||
/// The current access method
|
||||
pub ac: Option<String>,
|
||||
/// The current authentication token
|
||||
pub tk: Option<Value>,
|
||||
/// The current scope authentication data
|
||||
pub sd: Option<Value>,
|
||||
/// The current record authentication data
|
||||
pub rd: Option<Value>,
|
||||
/// The current expiration time of the session
|
||||
pub exp: Option<i64>,
|
||||
}
|
||||
|
@ -46,9 +46,9 @@ impl Session {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the selected database for the session
|
||||
pub fn with_sc(mut self, sc: &str) -> Session {
|
||||
self.sc = Some(sc.to_owned());
|
||||
/// Set the selected access method for the session
|
||||
pub fn with_ac(mut self, ac: &str) -> Session {
|
||||
self.ac = Some(ac.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -84,26 +84,26 @@ impl Session {
|
|||
|
||||
/// Convert a session into a runtime
|
||||
pub(crate) fn context<'a>(&self, mut ctx: Context<'a>) -> Context<'a> {
|
||||
// Add scope auth data
|
||||
let val: Value = self.sd.to_owned().into();
|
||||
// Add access method data
|
||||
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);
|
||||
// Add scope data
|
||||
let val: Value = self.sc.to_owned().into();
|
||||
ctx.add_value("scope", val);
|
||||
// Add token data
|
||||
let val: Value = self.tk.to_owned().into();
|
||||
ctx.add_value("token", val);
|
||||
// Add session value
|
||||
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(),
|
||||
"id".to_string() => self.id.to_owned().into(),
|
||||
"ip".to_string() => self.ip.to_owned().into(),
|
||||
"ns".to_string() => self.ns.to_owned().into(),
|
||||
"or".to_string() => self.or.to_owned().into(),
|
||||
"sc".to_string() => self.sc.to_owned().into(),
|
||||
"sd".to_string() => self.sd.to_owned().into(),
|
||||
"rd".to_string() => self.rd.to_owned().into(),
|
||||
"tk".to_string() => self.tk.to_owned().into(),
|
||||
"exp".to_string() => self.exp.to_owned().into(),
|
||||
});
|
||||
ctx.add_value("session", val);
|
||||
// Output context
|
||||
|
@ -133,19 +133,19 @@ impl Session {
|
|||
sess
|
||||
}
|
||||
|
||||
/// Create a scoped session for a given NS and DB
|
||||
pub fn for_scope(ns: &str, db: &str, sc: &str, rid: Value) -> Session {
|
||||
/// Create a record user session for a given NS and DB
|
||||
pub fn for_record(ns: &str, db: &str, ac: &str, rid: Value) -> 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,
|
||||
ip: None,
|
||||
or: None,
|
||||
id: None,
|
||||
ns: Some(ns.to_owned()),
|
||||
db: Some(db.to_owned()),
|
||||
sc: Some(sc.to_owned()),
|
||||
tk: None,
|
||||
sd: Some(rid),
|
||||
rd: Some(rid),
|
||||
exp: None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ use crate::doc::CursorDoc;
|
|||
use crate::doc::Document;
|
||||
use crate::err::Error;
|
||||
use crate::fflags::FFLAGS;
|
||||
use crate::sql::paths::AC;
|
||||
use crate::sql::paths::META;
|
||||
use crate::sql::paths::SC;
|
||||
use crate::sql::paths::SD;
|
||||
use crate::sql::paths::RD;
|
||||
use crate::sql::paths::TK;
|
||||
use crate::sql::permission::Permission;
|
||||
use crate::sql::statements::LiveStatement;
|
||||
|
@ -161,8 +161,8 @@ impl<'a> Document<'a> {
|
|||
// This ensures that we are using the session
|
||||
// of the user who created the LIVE query.
|
||||
let mut lqctx = Context::background();
|
||||
lqctx.add_value("auth", sess.pick(SD.as_ref()));
|
||||
lqctx.add_value("scope", sess.pick(SC.as_ref()));
|
||||
lqctx.add_value("access", sess.pick(AC.as_ref()));
|
||||
lqctx.add_value("auth", sess.pick(RD.as_ref()));
|
||||
lqctx.add_value("token", sess.pick(TK.as_ref()));
|
||||
lqctx.add_value("session", sess);
|
||||
// We need to create a new options which we will
|
||||
|
|
|
@ -299,9 +299,9 @@ pub enum Error {
|
|||
value: String,
|
||||
},
|
||||
|
||||
/// The requested namespace token does not exist
|
||||
#[error("The namespace token '{value}' does not exist")]
|
||||
NtNotFound {
|
||||
/// The requested namespace access method does not exist
|
||||
#[error("The namespace access method '{value}' does not exist")]
|
||||
NaNotFound {
|
||||
value: String,
|
||||
},
|
||||
|
||||
|
@ -317,9 +317,9 @@ pub enum Error {
|
|||
value: String,
|
||||
},
|
||||
|
||||
/// The requested database token does not exist
|
||||
#[error("The database token '{value}' does not exist")]
|
||||
DtNotFound {
|
||||
/// The requested database access method does not exist
|
||||
#[error("The database access method '{value}' does not exist")]
|
||||
DaNotFound {
|
||||
value: String,
|
||||
},
|
||||
|
||||
|
@ -353,12 +353,6 @@ pub enum Error {
|
|||
value: String,
|
||||
},
|
||||
|
||||
/// The requested scope does not exist
|
||||
#[error("The scope '{value}' does not exist")]
|
||||
ScNotFound {
|
||||
value: String,
|
||||
},
|
||||
|
||||
// The cluster node already exists
|
||||
#[error("The node '{value}' already exists")]
|
||||
ClAlreadyExists {
|
||||
|
@ -371,12 +365,6 @@ pub enum Error {
|
|||
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
|
||||
#[error("The param '${value}' does not exist")]
|
||||
PaNotFound {
|
||||
|
@ -764,15 +752,6 @@ pub enum Error {
|
|||
#[error("The signin query failed")]
|
||||
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")]
|
||||
MissingUserOrPass,
|
||||
|
||||
|
@ -864,12 +843,6 @@ pub enum Error {
|
|||
value: String,
|
||||
},
|
||||
|
||||
/// The requested scope already exists
|
||||
#[error("The scope '{value}' already exists")]
|
||||
ScAlreadyExists {
|
||||
value: String,
|
||||
},
|
||||
|
||||
/// The requested table already exists
|
||||
#[error("The table '{value}' already exists")]
|
||||
TbAlreadyExists {
|
||||
|
@ -888,12 +861,6 @@ pub enum Error {
|
|||
value: String,
|
||||
},
|
||||
|
||||
/// The requested scope token already exists
|
||||
#[error("The scope token '{value}' already exists")]
|
||||
StAlreadyExists {
|
||||
value: String,
|
||||
},
|
||||
|
||||
/// The requested user already exists
|
||||
#[error("The user '{value}' already exists")]
|
||||
UserRootAlreadyExists {
|
||||
|
@ -921,14 +888,6 @@ pub enum Error {
|
|||
#[error("The session has expired")]
|
||||
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
|
||||
#[error("A node task has failed: {0}")]
|
||||
NodeAgent(&'static str),
|
||||
|
@ -940,6 +899,70 @@ pub enum Error {
|
|||
/// The supplied type could not be serialiazed into `sql::Value`
|
||||
#[error("Serialization error: {0}")]
|
||||
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 {
|
||||
|
|
|
@ -230,13 +230,13 @@ pub fn synchronous(ctx: &Context<'_>, name: &str, args: Vec<Value>) -> Result<Va
|
|||
"rand::uuid::v7" => rand::uuid::v7,
|
||||
"rand::uuid" => rand::uuid,
|
||||
//
|
||||
"session::ac" => session::ac(ctx),
|
||||
"session::db" => session::db(ctx),
|
||||
"session::id" => session::id(ctx),
|
||||
"session::ip" => session::ip(ctx),
|
||||
"session::ns" => session::ns(ctx),
|
||||
"session::origin" => session::origin(ctx),
|
||||
"session::sc" => session::sc(ctx),
|
||||
"session::sd" => session::sd(ctx),
|
||||
"session::rd" => session::rd(ctx),
|
||||
"session::token" => session::token(ctx),
|
||||
//
|
||||
"string::concat" => string::concat,
|
||||
|
|
|
@ -12,7 +12,7 @@ impl_module_def!(
|
|||
"ip" => run,
|
||||
"ns" => run,
|
||||
"origin" => run,
|
||||
"sc" => run,
|
||||
"sd" => run,
|
||||
"ac" => run,
|
||||
"rd" => run,
|
||||
"token" => run
|
||||
);
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
use crate::ctx::Context;
|
||||
use crate::err::Error;
|
||||
use crate::sql::paths::AC;
|
||||
use crate::sql::paths::DB;
|
||||
use crate::sql::paths::ID;
|
||||
use crate::sql::paths::IP;
|
||||
use crate::sql::paths::NS;
|
||||
use crate::sql::paths::OR;
|
||||
use crate::sql::paths::SC;
|
||||
use crate::sql::paths::SD;
|
||||
use crate::sql::paths::RD;
|
||||
use crate::sql::paths::TK;
|
||||
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> {
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn sc(ctx: &Context, _: ()) -> Result<Value, Error> {
|
||||
ctx.value("session").unwrap_or(&Value::None).pick(SC.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 rd(ctx: &Context, _: ()) -> Result<Value, Error> {
|
||||
ctx.value("session").unwrap_or(&Value::None).pick(RD.as_ref()).ok()
|
||||
}
|
||||
|
||||
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 serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -49,9 +49,9 @@ impl Auth {
|
|||
matches!(self.level(), Level::Database(_, _))
|
||||
}
|
||||
|
||||
/// Check if the current level is Scope
|
||||
pub fn is_scope(&self) -> bool {
|
||||
matches!(self.level(), Level::Scope(_, _, _))
|
||||
/// Check if the current level is Record
|
||||
pub fn is_record(&self) -> bool {
|
||||
matches!(self.level(), Level::Record(_, _, _))
|
||||
}
|
||||
|
||||
/// System Auth helpers
|
||||
|
@ -70,8 +70,8 @@ impl Auth {
|
|||
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 {
|
||||
Self::new(Actor::new(rid, vec![], (ns, db, sc).into()))
|
||||
pub fn for_record(rid: String, ns: &str, db: &str, ac: &str) -> Self {
|
||||
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 {
|
||||
fn from(val: (&DefineTokenStatement, Level)) -> Self {
|
||||
impl std::convert::From<(&DefineAccessStatement, Level)> for Auth {
|
||||
fn from(val: (&DefineAccessStatement, Level)) -> Self {
|
||||
Self::new((val.0, val.1).into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||
pub fn clear(session: &mut Session) -> Result<(), Error> {
|
||||
session.au = Arc::new(Auth::default());
|
||||
session.tk = None;
|
||||
session.sc = None;
|
||||
session.sd = None;
|
||||
session.ac = None;
|
||||
session.rd = None;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use super::{Level, Resource, ResourceKind};
|
||||
use crate::iam::Role;
|
||||
use crate::sql::statements::{DefineTokenStatement, DefineUserStatement};
|
||||
use crate::sql::statements::{DefineAccessStatement, DefineUserStatement};
|
||||
|
||||
//
|
||||
// User
|
||||
|
@ -124,8 +124,8 @@ impl std::convert::From<(&DefineUserStatement, Level)> for Actor {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<(&DefineTokenStatement, Level)> for Actor {
|
||||
fn from(val: (&DefineTokenStatement, Level)) -> Self {
|
||||
impl std::convert::From<(&DefineAccessStatement, Level)> for Actor {
|
||||
fn from(val: (&DefineAccessStatement, Level)) -> Self {
|
||||
Self::new(val.0.name.to_string(), Vec::default(), val.1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ pub enum Level {
|
|||
Root,
|
||||
Namespace(String),
|
||||
Database(String, String),
|
||||
Scope(String, String, String),
|
||||
Record(String, String, String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Level {
|
||||
|
@ -27,7 +27,7 @@ impl std::fmt::Display for Level {
|
|||
Level::Root => write!(f, "/"),
|
||||
Level::Namespace(ns) => write!(f, "/ns:{ns}/"),
|
||||
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::Namespace(_) => "Namespace",
|
||||
Level::Database(_, _) => "Database",
|
||||
Level::Scope(_, _, _) => "Scope",
|
||||
Level::Record(_, _, _) => "Record",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ impl Level {
|
|||
match self {
|
||||
Level::Namespace(ns) => Some(ns),
|
||||
Level::Database(ns, _) => Some(ns),
|
||||
Level::Scope(ns, _, _) => Some(ns),
|
||||
Level::Record(ns, _, _) => Some(ns),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -55,14 +55,14 @@ impl Level {
|
|||
pub fn db(&self) -> Option<&str> {
|
||||
match self {
|
||||
Level::Database(_, db) => Some(db),
|
||||
Level::Scope(_, db, _) => Some(db),
|
||||
Level::Record(_, db, _) => Some(db),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scope(&self) -> Option<&str> {
|
||||
pub fn id(&self) -> Option<&str> {
|
||||
match self {
|
||||
Level::Scope(_, _, scope) => Some(scope),
|
||||
Level::Record(_, _, id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ impl Level {
|
|||
Level::Root => None,
|
||||
Level::Namespace(_) => Some(Level::Root),
|
||||
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()));
|
||||
}
|
||||
|
||||
if let Some(scope) = self.scope() {
|
||||
attrs.insert("scope".into(), RestrictedExpression::new_string(scope.to_owned()));
|
||||
if let Some(id) = self.id() {
|
||||
attrs.insert("id".into(), RestrictedExpression::new_string(id.to_owned()));
|
||||
}
|
||||
|
||||
attrs
|
||||
|
@ -139,8 +139,8 @@ impl From<(&str, &str)> for Level {
|
|||
}
|
||||
|
||||
impl From<(&str, &str, &str)> for Level {
|
||||
fn from((ns, db, sc): (&str, &str, &str)) -> Self {
|
||||
Level::Scope(ns.to_owned(), db.to_owned(), sc.to_owned())
|
||||
fn from((ns, db, id): (&str, &str, &str)) -> Self {
|
||||
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(),
|
||||
(Some(ns), None, None) => (ns,).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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ pub enum ResourceKind {
|
|||
Any,
|
||||
Namespace,
|
||||
Database,
|
||||
Scope,
|
||||
Record,
|
||||
Table,
|
||||
Document,
|
||||
Option,
|
||||
|
@ -40,7 +40,7 @@ impl std::fmt::Display for ResourceKind {
|
|||
ResourceKind::Any => write!(f, "Any"),
|
||||
ResourceKind::Namespace => write!(f, "Namespace"),
|
||||
ResourceKind::Database => write!(f, "Database"),
|
||||
ResourceKind::Scope => write!(f, "Scope"),
|
||||
ResourceKind::Record => write!(f, "Record"),
|
||||
ResourceKind::Table => write!(f, "Table"),
|
||||
ResourceKind::Document => write!(f, "Document"),
|
||||
ResourceKind::Option => write!(f, "Option"),
|
||||
|
@ -74,8 +74,8 @@ impl ResourceKind {
|
|||
self.on_level((ns, db).into())
|
||||
}
|
||||
|
||||
pub fn on_scope(self, ns: &str, db: &str, scope: &str) -> Resource {
|
||||
self.on_level((ns, db, scope).into())
|
||||
pub fn on_record(self, ns: &str, db: &str, rid: &str) -> Resource {
|
||||
self.on_level((ns, db, rid).into())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ pub static DEFAULT_CEDAR_SCHEMA: Lazy<serde_json::Value> = Lazy::new(|| {
|
|||
},
|
||||
},
|
||||
"entityTypes": {
|
||||
// Represents the Root, Namespace, Database and Scope levels
|
||||
// Represents the Root, Namespace, Database and Record levels
|
||||
"Level": {
|
||||
"shape": {
|
||||
"type": "Record",
|
||||
|
@ -24,7 +24,7 @@ pub static DEFAULT_CEDAR_SCHEMA: Lazy<serde_json::Value> = Lazy::new(|| {
|
|||
"type": { "type": "String", "required": true },
|
||||
"ns": { "type": "String", "required": false },
|
||||
"db": { "type": "String", "required": false },
|
||||
"scope": { "type": "String", "required": false },
|
||||
"rid": { "type": "String", "required": false },
|
||||
"table": { "type": "String", "required": false },
|
||||
"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"]},
|
||||
"Namespace": {"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"]},
|
||||
"Document": {"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": {
|
||||
"appliesTo": {
|
||||
"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": {
|
||||
"appliesTo": {
|
||||
"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 clear;
|
||||
pub mod entities;
|
||||
pub mod issue;
|
||||
#[cfg(feature = "jwks")]
|
||||
pub mod jwks;
|
||||
pub mod policies;
|
||||
|
|
|
@ -24,7 +24,7 @@ pub static POLICY_SET: Lazy<PolicySet> = Lazy::new(|| {
|
|||
) when {
|
||||
principal.roles.contains(Role::"Editor") &&
|
||||
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
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use super::verify::{verify_creds_legacy, verify_db_creds, verify_ns_creds, verify_root_creds};
|
||||
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::err::Error;
|
||||
use crate::iam::issue::{config, expiration};
|
||||
use crate::iam::token::{Claims, HEADER};
|
||||
use crate::iam::Auth;
|
||||
use crate::kvs::{Datastore, LockType::*, TransactionType::*};
|
||||
use crate::sql::AccessType;
|
||||
use crate::sql::Object;
|
||||
use crate::sql::Value;
|
||||
use chrono::{Duration, Utc};
|
||||
use jsonwebtoken::{encode, EncodingKey};
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -21,20 +23,20 @@ pub async fn signin(
|
|||
// Parse the specified variables
|
||||
let ns = vars.get("NS").or_else(|| vars.get("ns"));
|
||||
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
|
||||
match (ns, db, sc) {
|
||||
// SCOPE signin
|
||||
(Some(ns), Some(db), Some(sc)) => {
|
||||
match (ns, db, ac) {
|
||||
// DB signin with access method
|
||||
(Some(ns), Some(db), Some(ac)) => {
|
||||
// Process the provided values
|
||||
let ns = ns.to_raw_string();
|
||||
let db = db.to_raw_string();
|
||||
let sc = sc.to_raw_string();
|
||||
// Attempt to signin to specified scope
|
||||
super::signin::sc(kvs, session, ns, db, sc, vars).await
|
||||
let ac = ac.to_raw_string();
|
||||
// Attempt to signin using specified access method
|
||||
super::signin::db_access(kvs, session, ns, db, ac, vars).await
|
||||
}
|
||||
// DB signin
|
||||
// DB signin with user credentials
|
||||
(Some(ns), Some(db), None) => {
|
||||
// Get the provided user and pass
|
||||
let user = vars.get("user");
|
||||
|
@ -49,12 +51,12 @@ pub async fn signin(
|
|||
let user = user.to_raw_string();
|
||||
let pass = pass.to_raw_string();
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
// NS signin
|
||||
// NS signin with user credentials
|
||||
(Some(ns), None, None) => {
|
||||
// Get the provided user and pass
|
||||
let user = vars.get("user");
|
||||
|
@ -68,12 +70,12 @@ pub async fn signin(
|
|||
let user = user.to_raw_string();
|
||||
let pass = pass.to_raw_string();
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
// KV signin
|
||||
// ROOT signin with user credentials
|
||||
(None, None, None) => {
|
||||
// Get the provided user and pass
|
||||
let user = vars.get("user");
|
||||
|
@ -86,7 +88,7 @@ pub async fn signin(
|
|||
let user = user.to_raw_string();
|
||||
let pass = pass.to_raw_string();
|
||||
// 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),
|
||||
}
|
||||
|
@ -95,25 +97,36 @@ pub async fn signin(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn sc(
|
||||
pub async fn db_access(
|
||||
kvs: &Datastore,
|
||||
session: &mut Session,
|
||||
ns: String,
|
||||
db: String,
|
||||
sc: String,
|
||||
ac: String,
|
||||
vars: Object,
|
||||
) -> Result<Option<String>, Error> {
|
||||
// Create a new readonly transaction
|
||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Fetch the specified scope from storage
|
||||
let scope = tx.get_sc(&ns, &db, &sc).await;
|
||||
// Fetch the specified access method from storage
|
||||
let access = tx.get_db_access(&ns, &db, &ac).await;
|
||||
// Ensure that the transaction is cancelled
|
||||
tx.cancel().await?;
|
||||
// Check if the supplied Scope login exists
|
||||
match scope {
|
||||
Ok(sv) => {
|
||||
match sv.signin {
|
||||
// This scope allows signin
|
||||
// Check the provided access method exists
|
||||
match access {
|
||||
Ok(av) => {
|
||||
// Check the access method type
|
||||
// All access method types are supported except for JWT
|
||||
// The JWT access method is the one that is internal to SurrealDB
|
||||
// The equivalent of signing in with JWT is to authenticate it
|
||||
match av.kind {
|
||||
AccessType::Record(at) => {
|
||||
// Check if the record access method supports issuing tokens
|
||||
let iss = match at.jwt.issue {
|
||||
Some(iss) => iss,
|
||||
_ => return Err(Error::AccessMethodMismatch),
|
||||
};
|
||||
match at.signin {
|
||||
// This record access allows signin
|
||||
Some(val) => {
|
||||
// Setup the query params
|
||||
let vars = Some(vars.0);
|
||||
|
@ -124,60 +137,43 @@ pub async fn sc(
|
|||
// Compute the value with the params
|
||||
match kvs.evaluate(val, &sess, vars).await {
|
||||
// The signin value succeeded
|
||||
Ok(val) => match val.record() {
|
||||
Ok(val) => {
|
||||
match val.record() {
|
||||
// There is a record returned
|
||||
Some(rid) => {
|
||||
// Create the authentication key
|
||||
let key = EncodingKey::from_secret(sv.code.as_ref());
|
||||
let key = config(iss.alg, iss.key)?;
|
||||
// Create the authentication claim
|
||||
let exp = Some(
|
||||
match sv.session {
|
||||
Some(v) => {
|
||||
// The defined session duration must be valid
|
||||
match Duration::from_std(v.0) {
|
||||
// The resulting session expiration must be valid
|
||||
Ok(d) => match Utc::now().checked_add_signed(d) {
|
||||
Some(exp) => exp,
|
||||
None => {
|
||||
return Err(Error::InvalidSessionExpiration)
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
return Err(Error::InvalidSessionDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Utc::now() + Duration::hours(1),
|
||||
}
|
||||
.timestamp(),
|
||||
);
|
||||
let val = Claims {
|
||||
iss: Some(SERVER_NAME.to_owned()),
|
||||
iat: Some(Utc::now().timestamp()),
|
||||
nbf: Some(Utc::now().timestamp()),
|
||||
exp,
|
||||
// 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()),
|
||||
sc: Some(sc.to_owned()),
|
||||
ac: Some(ac.to_owned()),
|
||||
id: Some(rid.to_raw()),
|
||||
..Claims::default()
|
||||
};
|
||||
// Log the authenticated scope info
|
||||
trace!("Signing in to scope `{}`", sc);
|
||||
// Log the authenticated access method info
|
||||
trace!("Signing in with access method `{}`", ac);
|
||||
// Create the authentication token
|
||||
let enc = encode(&HEADER, &val, &key);
|
||||
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.sc = Some(sc.to_owned());
|
||||
session.sd = Some(Value::from(rid.to_owned()));
|
||||
session.exp = exp;
|
||||
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::Scope(ns, db, sc),
|
||||
Level::Record(ns, db, rid.to_string()),
|
||||
)));
|
||||
// Check the authentication token
|
||||
match enc {
|
||||
|
@ -187,22 +183,26 @@ pub async fn sc(
|
|||
}
|
||||
}
|
||||
_ => Err(Error::NoRecordFound),
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
Error::Thrown(_) => Err(e),
|
||||
e if *INSECURE_FORWARD_SCOPE_ERRORS => Err(e),
|
||||
_ => Err(Error::SigninQueryFailed),
|
||||
e if *INSECURE_FORWARD_RECORD_ACCESS_ERRORS => Err(e),
|
||||
_ => Err(Error::AccessRecordSigninQueryFailed),
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => Err(Error::ScopeNoSignin),
|
||||
_ => Err(Error::AccessRecordNoSignin),
|
||||
}
|
||||
}
|
||||
_ => Err(Error::NoScopeFound),
|
||||
_ => Err(Error::AccessMethodMismatch),
|
||||
}
|
||||
}
|
||||
_ => Err(Error::AccessNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn db(
|
||||
pub async fn db_user(
|
||||
kvs: &Datastore,
|
||||
session: &mut Session,
|
||||
ns: String,
|
||||
|
@ -258,7 +258,7 @@ pub async fn db(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn ns(
|
||||
pub async fn ns_user(
|
||||
kvs: &Datastore,
|
||||
session: &mut Session,
|
||||
ns: String,
|
||||
|
@ -312,7 +312,7 @@ pub async fn ns(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn root(
|
||||
pub async fn root_user(
|
||||
kvs: &Datastore,
|
||||
session: &mut Session,
|
||||
user: String,
|
||||
|
@ -370,14 +370,14 @@ mod tests {
|
|||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_signin_scope() {
|
||||
async fn test_signin_record() {
|
||||
// Test with correct credentials
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute(
|
||||
r#"
|
||||
DEFINE SCOPE user SESSION 1h
|
||||
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1h
|
||||
SIGNIN (
|
||||
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();
|
||||
vars.insert("user", "user".into());
|
||||
vars.insert("pass", "pass".into());
|
||||
let res = sc(
|
||||
let res = db_access(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
|
@ -422,10 +422,11 @@ mod tests {
|
|||
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_scope());
|
||||
assert!(sess.au.is_record());
|
||||
assert_eq!(sess.au.level().ns(), 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::Editor), "Auth user expected to not have Editor 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();
|
||||
assert!(
|
||||
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");
|
||||
ds.execute(
|
||||
r#"
|
||||
DEFINE SCOPE user SESSION 1h
|
||||
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1h
|
||||
SIGNIN (
|
||||
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();
|
||||
vars.insert("user", "user".into());
|
||||
vars.insert("pass", "incorrect".into());
|
||||
let res = sc(
|
||||
let res = db_access(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
|
@ -492,7 +493,161 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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
|
||||
//
|
||||
|
@ -507,7 +662,7 @@ mod tests {
|
|||
db: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let res = db(
|
||||
let res = db_user(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
|
@ -546,7 +701,7 @@ mod tests {
|
|||
db: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let res = db(
|
||||
let res = db_user(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
|
@ -578,7 +733,7 @@ mod tests {
|
|||
let mut sess = Session {
|
||||
..Default::default()
|
||||
};
|
||||
let res = db(
|
||||
let res = db_user(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
|
@ -593,7 +748,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_signin_ns() {
|
||||
async fn test_signin_ns_user() {
|
||||
//
|
||||
// Test without roles defined
|
||||
//
|
||||
|
@ -608,7 +763,7 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
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;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||
|
@ -638,7 +793,7 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
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;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||
|
@ -661,8 +816,13 @@ mod tests {
|
|||
let mut sess = Session {
|
||||
..Default::default()
|
||||
};
|
||||
let res =
|
||||
ns(&ds, &mut sess, "test".to_string(), "user".to_string(), "invalid".to_string())
|
||||
let res = ns_user(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
"user".to_string(),
|
||||
"invalid".to_string(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
|
||||
|
@ -670,7 +830,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_signin_root() {
|
||||
async fn test_signin_root_user() {
|
||||
//
|
||||
// Test without roles defined
|
||||
//
|
||||
|
@ -683,7 +843,7 @@ mod tests {
|
|||
let mut sess = Session {
|
||||
..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_eq!(sess.au.id(), "user");
|
||||
|
@ -708,7 +868,7 @@ mod tests {
|
|||
let mut sess = Session {
|
||||
..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_eq!(sess.au.id(), "user");
|
||||
|
@ -728,7 +888,7 @@ mod tests {
|
|||
let mut sess = Session {
|
||||
..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);
|
||||
}
|
||||
|
|
|
@ -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::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::{Actor, Level};
|
||||
use crate::kvs::{Datastore, LockType::*, TransactionType::*};
|
||||
use crate::sql::AccessType;
|
||||
use crate::sql::Object;
|
||||
use crate::sql::Value;
|
||||
use chrono::{Duration, Utc};
|
||||
use jsonwebtoken::{encode, EncodingKey};
|
||||
use chrono::Utc;
|
||||
use jsonwebtoken::{encode, Header};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -20,106 +22,99 @@ pub async fn signup(
|
|||
// Parse the specified variables
|
||||
let ns = vars.get("NS").or_else(|| vars.get("ns"));
|
||||
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
|
||||
match (ns, db, sc) {
|
||||
(Some(ns), Some(db), Some(sc)) => {
|
||||
match (ns, db, ac) {
|
||||
(Some(ns), Some(db), Some(ac)) => {
|
||||
// Process the provided values
|
||||
let ns = ns.to_raw_string();
|
||||
let db = db.to_raw_string();
|
||||
let sc = sc.to_raw_string();
|
||||
// Attempt to signup to specified scope
|
||||
super::signup::sc(kvs, session, ns, db, sc, vars).await
|
||||
let ac = ac.to_raw_string();
|
||||
// Attempt to signup using specified access method
|
||||
// Currently, signup is only supported at the database level
|
||||
super::signup::db_access(kvs, session, ns, db, ac, vars).await
|
||||
}
|
||||
_ => Err(Error::InvalidSignup),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sc(
|
||||
pub async fn db_access(
|
||||
kvs: &Datastore,
|
||||
session: &mut Session,
|
||||
ns: String,
|
||||
db: String,
|
||||
sc: String,
|
||||
ac: String,
|
||||
vars: Object,
|
||||
) -> Result<Option<String>, Error> {
|
||||
// Create a new readonly transaction
|
||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Fetch the specified scope from storage
|
||||
let scope = tx.get_sc(&ns, &db, &sc).await;
|
||||
// Fetch the specified access method from storage
|
||||
let access = tx.get_db_access(&ns, &db, &ac).await;
|
||||
// Ensure that the transaction is cancelled
|
||||
tx.cancel().await?;
|
||||
// Check if the supplied Scope login exists
|
||||
match scope {
|
||||
Ok(sv) => {
|
||||
match sv.signup {
|
||||
// This scope allows signup
|
||||
// Check the provided access method exists
|
||||
match access {
|
||||
Ok(av) => {
|
||||
// Check the access method type
|
||||
// Currently, only the record access method supports signup
|
||||
match av.kind {
|
||||
AccessType::Record(at) => {
|
||||
// Check if the record access method supports issuing tokens
|
||||
let iss = match at.jwt.issue {
|
||||
Some(iss) => iss,
|
||||
_ => return Err(Error::AccessMethodMismatch),
|
||||
};
|
||||
match at.signup {
|
||||
// This record access allows signup
|
||||
Some(val) => {
|
||||
// Setup the query params
|
||||
let vars = Some(vars.0);
|
||||
// Setup the system session for creating the signup record
|
||||
// Setup the system session for finding the signup record
|
||||
let mut sess = Session::editor().with_ns(&ns).with_db(&db);
|
||||
sess.ip.clone_from(&session.ip);
|
||||
sess.or.clone_from(&session.or);
|
||||
// Compute the value with the params
|
||||
match kvs.evaluate(val, &sess, vars).await {
|
||||
// The signin value succeeded
|
||||
Ok(val) => match val.record() {
|
||||
Ok(val) => {
|
||||
match val.record() {
|
||||
// There is a record returned
|
||||
Some(rid) => {
|
||||
// Create the authentication key
|
||||
let key = EncodingKey::from_secret(sv.code.as_ref());
|
||||
let key = config(iss.alg, iss.key)?;
|
||||
// Create the authentication claim
|
||||
let exp = Some(
|
||||
match sv.session {
|
||||
Some(v) => {
|
||||
// The defined session duration must be valid
|
||||
match Duration::from_std(v.0) {
|
||||
// The resulting session expiration must be valid
|
||||
Ok(d) => match Utc::now().checked_add_signed(d) {
|
||||
Some(exp) => exp,
|
||||
None => {
|
||||
return Err(Error::InvalidSessionExpiration)
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
return Err(Error::InvalidSessionDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Utc::now() + Duration::hours(1),
|
||||
}
|
||||
.timestamp(),
|
||||
);
|
||||
let val = Claims {
|
||||
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()),
|
||||
exp,
|
||||
ns: Some(ns.to_owned()),
|
||||
db: Some(db.to_owned()),
|
||||
sc: Some(sc.to_owned()),
|
||||
ac: Some(ac.to_owned()),
|
||||
id: Some(rid.to_raw()),
|
||||
..Claims::default()
|
||||
};
|
||||
// Log the authenticated scope info
|
||||
trace!("Signing up to scope `{}`", sc);
|
||||
// Log the authenticated access method info
|
||||
trace!("Signing up with access method `{}`", ac);
|
||||
// Create the authentication token
|
||||
let enc = encode(&HEADER, &val, &key);
|
||||
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.sc = Some(sc.to_owned());
|
||||
session.sd = Some(Value::from(rid.to_owned()));
|
||||
session.exp = exp;
|
||||
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::Scope(ns, db, sc),
|
||||
Level::Record(ns, db, rid.to_string()),
|
||||
)));
|
||||
// Create the authentication token
|
||||
// Check the authentication token
|
||||
match enc {
|
||||
// The auth token was created successfully
|
||||
Ok(tk) => Ok(Some(tk)),
|
||||
|
@ -127,18 +122,22 @@ pub async fn sc(
|
|||
}
|
||||
}
|
||||
_ => Err(Error::NoRecordFound),
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
Error::Thrown(_) => Err(e),
|
||||
e if *INSECURE_FORWARD_SCOPE_ERRORS => Err(e),
|
||||
_ => Err(Error::SignupQueryFailed),
|
||||
e if *INSECURE_FORWARD_RECORD_ACCESS_ERRORS => Err(e),
|
||||
_ => Err(Error::AccessRecordSignupQueryFailed),
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => Err(Error::ScopeNoSignup),
|
||||
_ => Err(Error::AccessRecordNoSignup),
|
||||
}
|
||||
}
|
||||
_ => Err(Error::NoScopeFound),
|
||||
_ => Err(Error::AccessMethodMismatch),
|
||||
}
|
||||
}
|
||||
_ => Err(Error::AccessNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,17 +145,18 @@ pub async fn sc(
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::iam::Role;
|
||||
use chrono::Duration;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_scope_signup() {
|
||||
async fn test_record_signup() {
|
||||
// Test with valid parameters
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute(
|
||||
r#"
|
||||
DEFINE SCOPE user SESSION 1h
|
||||
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1h
|
||||
SIGNIN (
|
||||
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();
|
||||
vars.insert("user", "user".into());
|
||||
vars.insert("pass", "pass".into());
|
||||
let res = sc(
|
||||
let res = db_access(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
|
@ -196,10 +196,10 @@ mod tests {
|
|||
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_scope());
|
||||
assert!(sess.au.is_record());
|
||||
assert_eq!(sess.au.level().ns(), 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::Editor), "Auth user expected to not have Editor 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();
|
||||
assert!(
|
||||
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");
|
||||
ds.execute(
|
||||
r#"
|
||||
DEFINE SCOPE user SESSION 1h
|
||||
DEFINE ACCESS user ON DATABASE TYPE RECORD DURATION 1h
|
||||
SIGNIN (
|
||||
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();
|
||||
// Password is missing
|
||||
vars.insert("user", "user".into());
|
||||
let res = sc(
|
||||
let res = db_access(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
|
@ -259,4 +259,158 @@ mod tests {
|
|||
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(skip_serializing_if = "Option::is_none")]
|
||||
pub db: Option<String>,
|
||||
#[serde(alias = "sc")]
|
||||
#[serde(alias = "SC")]
|
||||
#[serde(rename = "SC")]
|
||||
#[serde(alias = "https://surrealdb.com/sc")]
|
||||
#[serde(alias = "https://surrealdb.com/scope")]
|
||||
#[serde(alias = "ac")]
|
||||
#[serde(alias = "AC")]
|
||||
#[serde(rename = "AC")]
|
||||
#[serde(alias = "https://surrealdb.com/ac")]
|
||||
#[serde(alias = "https://surrealdb.com/access")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sc: 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>,
|
||||
pub ac: Option<String>,
|
||||
#[serde(alias = "id")]
|
||||
#[serde(alias = "ID")]
|
||||
#[serde(rename = "ID")]
|
||||
|
@ -101,13 +94,9 @@ impl From<Claims> for Value {
|
|||
if let Some(db) = v.db {
|
||||
out.insert("DB".to_string(), db.into());
|
||||
}
|
||||
// Add SC field if set
|
||||
if let Some(sc) = v.sc {
|
||||
out.insert("SC".to_string(), sc.into());
|
||||
}
|
||||
// Add TK field if set
|
||||
if let Some(tk) = v.tk {
|
||||
out.insert("TK".to_string(), tk.into());
|
||||
// Add AC field if set
|
||||
if let Some(ac) = v.ac {
|
||||
out.insert("AC".to_string(), ac.into());
|
||||
}
|
||||
// Add ID field if set
|
||||
if let Some(id) = v.id {
|
||||
|
|
|
@ -4,94 +4,70 @@ use crate::err::Error;
|
|||
use crate::iam::jwks;
|
||||
use crate::iam::{token::Claims, Actor, Auth, Level, Role};
|
||||
use crate::kvs::{Datastore, LockType::*, TransactionType::*};
|
||||
use crate::sql::access_type::{AccessType, JwtAccessVerify};
|
||||
use crate::sql::{statements::DefineUserStatement, Algorithm, Value};
|
||||
use crate::syn;
|
||||
use argon2::{Argon2, PasswordHash, PasswordVerifier};
|
||||
use chrono::Utc;
|
||||
use jsonwebtoken::{decode, DecodingKey, Header, Validation};
|
||||
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::str::{self, FromStr};
|
||||
use std::sync::Arc;
|
||||
|
||||
async fn config(
|
||||
_kvs: &Datastore,
|
||||
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 {
|
||||
fn config(alg: Algorithm, key: String) -> Result<(DecodingKey, Validation), Error> {
|
||||
match alg {
|
||||
Algorithm::Hs256 => Ok((
|
||||
DecodingKey::from_secret(code.as_ref()),
|
||||
DecodingKey::from_secret(key.as_ref()),
|
||||
Validation::new(jsonwebtoken::Algorithm::HS256),
|
||||
)),
|
||||
Algorithm::Hs384 => Ok((
|
||||
DecodingKey::from_secret(code.as_ref()),
|
||||
DecodingKey::from_secret(key.as_ref()),
|
||||
Validation::new(jsonwebtoken::Algorithm::HS384),
|
||||
)),
|
||||
Algorithm::Hs512 => Ok((
|
||||
DecodingKey::from_secret(code.as_ref()),
|
||||
DecodingKey::from_secret(key.as_ref()),
|
||||
Validation::new(jsonwebtoken::Algorithm::HS512),
|
||||
)),
|
||||
Algorithm::EdDSA => Ok((
|
||||
DecodingKey::from_ed_pem(code.as_ref())?,
|
||||
DecodingKey::from_ed_pem(key.as_ref())?,
|
||||
Validation::new(jsonwebtoken::Algorithm::EdDSA),
|
||||
)),
|
||||
Algorithm::Es256 => Ok((
|
||||
DecodingKey::from_ec_pem(code.as_ref())?,
|
||||
DecodingKey::from_ec_pem(key.as_ref())?,
|
||||
Validation::new(jsonwebtoken::Algorithm::ES256),
|
||||
)),
|
||||
Algorithm::Es384 => Ok((
|
||||
DecodingKey::from_ec_pem(code.as_ref())?,
|
||||
DecodingKey::from_ec_pem(key.as_ref())?,
|
||||
Validation::new(jsonwebtoken::Algorithm::ES384),
|
||||
)),
|
||||
Algorithm::Es512 => Ok((
|
||||
DecodingKey::from_ec_pem(code.as_ref())?,
|
||||
DecodingKey::from_ec_pem(key.as_ref())?,
|
||||
Validation::new(jsonwebtoken::Algorithm::ES384),
|
||||
)),
|
||||
Algorithm::Ps256 => Ok((
|
||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||
Validation::new(jsonwebtoken::Algorithm::PS256),
|
||||
)),
|
||||
Algorithm::Ps384 => Ok((
|
||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||
Validation::new(jsonwebtoken::Algorithm::PS384),
|
||||
)),
|
||||
Algorithm::Ps512 => Ok((
|
||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||
Validation::new(jsonwebtoken::Algorithm::PS512),
|
||||
)),
|
||||
Algorithm::Rs256 => Ok((
|
||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||
Validation::new(jsonwebtoken::Algorithm::RS256),
|
||||
)),
|
||||
Algorithm::Rs384 => Ok((
|
||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||
Validation::new(jsonwebtoken::Algorithm::RS384),
|
||||
)),
|
||||
Algorithm::Rs512 => Ok((
|
||||
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||
DecodingKey::from_rsa_pem(key.as_ref())?,
|
||||
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
|
||||
match token_data.claims {
|
||||
// Check if this is scope token authentication
|
||||
// Check if this is record access
|
||||
Claims {
|
||||
ns: Some(ns),
|
||||
db: Some(db),
|
||||
sc: Some(sc),
|
||||
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),
|
||||
ac: Some(ac),
|
||||
id: Some(id),
|
||||
..
|
||||
} => {
|
||||
// Log the decoded authentication claims
|
||||
trace!("Authenticating to scope `{}`", sc);
|
||||
trace!("Authenticating with record access method `{}`", ac);
|
||||
// Create a new readonly transaction
|
||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Parse the record id
|
||||
let id = syn::thing(&id)?;
|
||||
// Get the scope
|
||||
let de = tx.get_sc(&ns, &db, &sc).await?;
|
||||
let cf = config_alg(Algorithm::Hs512, de.code)?;
|
||||
// Get the database access method
|
||||
let de = tx.get_db_access(&ns, &db, &ac).await?;
|
||||
// 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
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// Log the success
|
||||
debug!("Authenticated to scope `{}`", sc);
|
||||
debug!("Authenticated with record access method `{}`", ac);
|
||||
// Set the session
|
||||
session.tk = Some(value);
|
||||
session.ns = Some(ns.to_owned());
|
||||
session.db = Some(db.to_owned());
|
||||
session.sc = Some(sc.to_owned());
|
||||
session.sd = Some(Value::from(id.to_owned()));
|
||||
session.ac = Some(ac.to_owned());
|
||||
session.rd = Some(Value::from(id.to_owned()));
|
||||
session.exp = token_data.claims.exp;
|
||||
session.au = Arc::new(Auth::new(Actor::new(
|
||||
id.to_string(),
|
||||
Default::default(),
|
||||
Level::Scope(ns, db, sc),
|
||||
Level::Record(ns, db, id.to_string()),
|
||||
)));
|
||||
Ok(())
|
||||
}
|
||||
// Check if this is database token authentication
|
||||
// Check if this is database access
|
||||
Claims {
|
||||
ns: Some(ns),
|
||||
db: Some(db),
|
||||
tk: Some(tk),
|
||||
ac: Some(ac),
|
||||
..
|
||||
} => {
|
||||
// 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
|
||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Get the database token
|
||||
let de = tx.get_db_token(&ns, &db, &tk).await?;
|
||||
// Obtain the configuration with which to verify the token
|
||||
let cf = config(kvs, de.kind, de.code, token_data.header).await?;
|
||||
// Get the database access method
|
||||
let de = tx.get_db_access(&ns, &db, &ac).await?;
|
||||
// Obtain the configuration to verify the token based on the access method
|
||||
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
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// Parse the roles
|
||||
|
@ -320,11 +287,12 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
.collect::<Result<Vec<_>, _>>()?,
|
||||
};
|
||||
// Log the success
|
||||
debug!("Authenticated to database `{}` with token `{}`", db, tk);
|
||||
debug!("Authenticated to database `{}` with access method `{}`", db, ac);
|
||||
// Set the session
|
||||
session.tk = Some(value);
|
||||
session.ns = Some(ns.to_owned());
|
||||
session.db = Some(db.to_owned());
|
||||
session.ac = Some(ac.to_owned());
|
||||
session.exp = token_data.claims.exp;
|
||||
session.au = Arc::new(Auth::new(Actor::new(
|
||||
de.name.to_string(),
|
||||
|
@ -333,7 +301,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
)));
|
||||
Ok(())
|
||||
}
|
||||
// Check if this is database authentication
|
||||
// Check if this is database authentication with user credentials
|
||||
Claims {
|
||||
ns: Some(ns),
|
||||
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}");
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
let cf = config_alg(Algorithm::Hs512, de.code)?;
|
||||
let cf = config(Algorithm::Hs512, de.code)?;
|
||||
// Verify the token
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// Log the success
|
||||
|
@ -366,20 +334,35 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
)));
|
||||
Ok(())
|
||||
}
|
||||
// Check if this is namespace token authentication
|
||||
// Check if this is namespace access
|
||||
Claims {
|
||||
ns: Some(ns),
|
||||
tk: Some(tk),
|
||||
ac: Some(ac),
|
||||
..
|
||||
} => {
|
||||
// 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
|
||||
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||
// Get the namespace token
|
||||
let de = tx.get_ns_token(&ns, &tk).await?;
|
||||
// Obtain the configuration with which to verify the token
|
||||
let cf = config(kvs, de.kind, de.code, token_data.header).await?;
|
||||
// Get the namespace access method
|
||||
let de = tx.get_ns_access(&ns, &ac).await?;
|
||||
// Obtain the configuration to verify the token based on the access method
|
||||
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
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// Parse the roles
|
||||
|
@ -395,16 +378,17 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
.collect::<Result<Vec<_>, _>>()?,
|
||||
};
|
||||
// Log the success
|
||||
trace!("Authenticated to namespace `{}` with token `{}`", ns, tk);
|
||||
trace!("Authenticated to namespace `{}` with access method `{}`", ns, ac);
|
||||
// Set the session
|
||||
session.tk = Some(value);
|
||||
session.ns = Some(ns.to_owned());
|
||||
session.ac = Some(ac.to_owned());
|
||||
session.exp = token_data.claims.exp;
|
||||
session.au =
|
||||
Arc::new(Auth::new(Actor::new(de.name.to_string(), roles, Level::Namespace(ns))));
|
||||
Ok(())
|
||||
}
|
||||
// Check if this is namespace authentication
|
||||
// Check if this is namespace authentication with user credentials
|
||||
Claims {
|
||||
ns: Some(ns),
|
||||
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}");
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
let cf = config_alg(Algorithm::Hs512, de.code)?;
|
||||
let cf = config(Algorithm::Hs512, de.code)?;
|
||||
// Verify the token
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// Log the success
|
||||
|
@ -435,7 +419,7 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
|||
)));
|
||||
Ok(())
|
||||
}
|
||||
// Check if this is root level authentication
|
||||
// Check if this is root authentication with user credentials
|
||||
Claims {
|
||||
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}");
|
||||
Error::InvalidAuth
|
||||
})?;
|
||||
let cf = config_alg(Algorithm::Hs512, de.code)?;
|
||||
let cf = config(Algorithm::Hs512, de.code)?;
|
||||
// Verify the token
|
||||
decode::<Claims>(token, &cf.0, &cf.1)?;
|
||||
// Log the success
|
||||
|
@ -814,7 +798,7 @@ mod tests {
|
|||
iat: Some(Utc::now().timestamp()),
|
||||
nbf: Some(Utc::now().timestamp()),
|
||||
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||
tk: Some("token".to_string()),
|
||||
ac: Some("token".to_string()),
|
||||
ns: Some("test".to_string()),
|
||||
..Claims::default()
|
||||
};
|
||||
|
@ -822,7 +806,7 @@ mod tests {
|
|||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
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,
|
||||
None,
|
||||
)
|
||||
|
@ -921,7 +905,7 @@ mod tests {
|
|||
iat: Some(Utc::now().timestamp()),
|
||||
nbf: Some(Utc::now().timestamp()),
|
||||
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||
tk: Some("token".to_string()),
|
||||
ac: Some("token".to_string()),
|
||||
ns: Some("test".to_string()),
|
||||
db: Some("test".to_string()),
|
||||
..Claims::default()
|
||||
|
@ -930,7 +914,8 @@ mod tests {
|
|||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
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,
|
||||
None,
|
||||
)
|
||||
|
@ -1023,7 +1008,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_token_scope() {
|
||||
async fn test_token_db_record() {
|
||||
let secret = "jwt_secret";
|
||||
let key = EncodingKey::from_secret(secret.as_ref());
|
||||
let claims = Claims {
|
||||
|
@ -1031,17 +1016,25 @@ mod tests {
|
|||
iat: Some(Utc::now().timestamp()),
|
||||
nbf: Some(Utc::now().timestamp()),
|
||||
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||
tk: Some("token".to_string()),
|
||||
ns: 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()
|
||||
};
|
||||
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
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,
|
||||
None,
|
||||
)
|
||||
|
@ -1050,7 +1043,7 @@ mod tests {
|
|||
|
||||
//
|
||||
// Test without roles defined
|
||||
// Roles should be ignored in scope authentication
|
||||
// Roles should be ignored in record access
|
||||
//
|
||||
{
|
||||
// Prepare the claims object
|
||||
|
@ -1065,9 +1058,9 @@ mod tests {
|
|||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.sc, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "token");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.ac, Some("token".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!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||
|
@ -1078,7 +1071,7 @@ mod tests {
|
|||
|
||||
//
|
||||
// Test with roles defined
|
||||
// Roles should be ignored in scope authentication
|
||||
// Roles should be ignored in record access
|
||||
//
|
||||
{
|
||||
// Prepare the claims object
|
||||
|
@ -1093,9 +1086,9 @@ mod tests {
|
|||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.sc, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "token");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.ac, Some("token".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!(!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
|
||||
let mut claims = claims.clone();
|
||||
claims.sc = Some("invalid".to_string());
|
||||
claims.ac = Some("invalid".to_string());
|
||||
// Create the token
|
||||
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||
// Signin with the token
|
||||
|
@ -1156,7 +1149,7 @@ mod tests {
|
|||
// Test with generic user identifier
|
||||
//
|
||||
{
|
||||
let resource_id = "user:`2k9qnabxuxh8k4d5gfto`".to_string();
|
||||
let resource_id = "user:⟨2k9qnabxuxh8k4d5gfto⟩".to_string();
|
||||
// Prepare the claims object
|
||||
let mut claims = claims.clone();
|
||||
claims.id = Some(resource_id.clone());
|
||||
|
@ -1169,11 +1162,11 @@ mod tests {
|
|||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.sc, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "token");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.ac, Some("token".to_string()));
|
||||
assert_eq!(sess.au.id(), resource_id);
|
||||
assert!(sess.au.is_record());
|
||||
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_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.sc, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "token");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.ac, Some("token".to_string()));
|
||||
assert_eq!(sess.au.id(), resource_id);
|
||||
assert!(sess.au.is_record());
|
||||
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_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.sc, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "token");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.ac, Some("token".to_string()));
|
||||
assert_eq!(sess.au.id(), resource_id);
|
||||
assert!(sess.au.is_record());
|
||||
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_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.sc, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "token");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.ac, Some("token".to_string()));
|
||||
assert_eq!(sess.au.id(), resource_id);
|
||||
assert!(sess.au.is_record());
|
||||
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_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.sc, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "token");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.ac, Some("token".to_string()));
|
||||
assert_eq!(sess.au.id(), resource_id);
|
||||
assert!(sess.au.is_record());
|
||||
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]
|
||||
async fn test_token_scope_custom_claims() {
|
||||
async fn test_token_db_record_custom_claims() {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let secret = "jwt_secret";
|
||||
|
@ -1295,7 +1288,15 @@ mod tests {
|
|||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
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,
|
||||
None,
|
||||
)
|
||||
|
@ -1315,10 +1316,10 @@ mod tests {
|
|||
"iat": {now},
|
||||
"nbf": {now},
|
||||
"exp": {later},
|
||||
"tk": "token",
|
||||
"ns": "test",
|
||||
"db": "test",
|
||||
"sc": "test",
|
||||
"ac": "token",
|
||||
"id": "user:test",
|
||||
|
||||
"string_claim": "test",
|
||||
"bool_claim": true,
|
||||
|
@ -1351,9 +1352,9 @@ mod tests {
|
|||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.sc, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "token");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.ac, Some("token".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!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||
|
@ -1387,7 +1388,7 @@ mod tests {
|
|||
|
||||
#[cfg(feature = "jwks")]
|
||||
#[tokio::test]
|
||||
async fn test_token_scope_jwks() {
|
||||
async fn test_token_db_record_jwks() {
|
||||
use crate::dbs::capabilities::{Capabilities, NetTarget, Targets};
|
||||
use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine};
|
||||
use jsonwebtoken::jwk::{Jwk, JwkSet};
|
||||
|
@ -1448,7 +1449,14 @@ mod tests {
|
|||
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute(
|
||||
format!("DEFINE TOKEN token ON SCOPE test TYPE JWKS VALUE '{server_url}/{jwks_path}';")
|
||||
format!(
|
||||
r#"
|
||||
DEFINE ACCESS token ON DATABASE TYPE RECORD
|
||||
WITH JWT URL '{server_url}/{jwks_path}';
|
||||
|
||||
CREATE user:test;
|
||||
"#
|
||||
)
|
||||
.as_str(),
|
||||
&sess,
|
||||
None,
|
||||
|
@ -1470,16 +1478,16 @@ mod tests {
|
|||
iat: Some(Utc::now().timestamp()),
|
||||
nbf: Some(Utc::now().timestamp()),
|
||||
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||
tk: Some("token".to_string()),
|
||||
ns: 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()
|
||||
};
|
||||
|
||||
//
|
||||
// Test without roles defined
|
||||
// Roles should be ignored in scope authentication
|
||||
// Roles should be ignored in record access
|
||||
//
|
||||
{
|
||||
// Prepare the claims object
|
||||
|
@ -1494,9 +1502,9 @@ mod tests {
|
|||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.sc, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "token");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.ac, Some("token".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!(!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::key_req::KeyRequirements;
|
||||
/// Stores a DEFINE TOKEN ON DATABASE config definition
|
||||
use derive::Key;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
||||
#[non_exhaustive]
|
||||
pub struct Tk<'a> {
|
||||
pub struct Ac<'a> {
|
||||
__: u8,
|
||||
_a: u8,
|
||||
pub ns: &'a str,
|
||||
|
@ -15,33 +15,33 @@ pub struct Tk<'a> {
|
|||
_c: u8,
|
||||
_d: 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> {
|
||||
Tk::new(ns, db, tk)
|
||||
pub fn new<'a>(ns: &'a str, db: &'a str, ac: &'a str) -> Ac<'a> {
|
||||
Ac::new(ns, db, ac)
|
||||
}
|
||||
|
||||
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't', b'k', 0x00]);
|
||||
k.extend_from_slice(&[b'!', b'a', 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't', b'k', 0xff]);
|
||||
k.extend_from_slice(&[b'!', b'a', b'c', 0xff]);
|
||||
k
|
||||
}
|
||||
|
||||
impl KeyRequirements for Tk<'_> {
|
||||
impl KeyRequirements for Ac<'_> {
|
||||
fn key_category(&self) -> KeyCategory {
|
||||
KeyCategory::DatabaseToken
|
||||
KeyCategory::DatabaseAccess
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Tk<'a> {
|
||||
pub fn new(ns: &'a str, db: &'a str, tk: &'a str) -> Self {
|
||||
impl<'a> Ac<'a> {
|
||||
pub fn new(ns: &'a str, db: &'a str, ac: &'a str) -> Self {
|
||||
Self {
|
||||
__: b'/',
|
||||
_a: b'*',
|
||||
|
@ -49,9 +49,9 @@ impl<'a> Tk<'a> {
|
|||
_b: b'*',
|
||||
db,
|
||||
_c: b'!',
|
||||
_d: b't',
|
||||
_e: b'k',
|
||||
tk,
|
||||
_d: b'a',
|
||||
_e: b'c',
|
||||
ac,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,27 +62,27 @@ mod tests {
|
|||
fn key() {
|
||||
use super::*;
|
||||
#[rustfmt::skip]
|
||||
let val = Tk::new(
|
||||
let val = Ac::new(
|
||||
"testns",
|
||||
"testdb",
|
||||
"testtk",
|
||||
"testac",
|
||||
);
|
||||
let enc = Tk::encode(&val).unwrap();
|
||||
assert_eq!(enc, b"/*testns\x00*testdb\x00!tktesttk\x00");
|
||||
let enc = Ac::encode(&val).unwrap();
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix() {
|
||||
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]
|
||||
fn test_suffix() {
|
||||
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 az;
|
||||
pub mod fc;
|
||||
pub mod ml;
|
||||
pub mod pa;
|
||||
pub mod sc;
|
||||
pub mod tb;
|
||||
pub mod ti;
|
||||
pub mod tk;
|
||||
pub mod ts;
|
||||
pub mod us;
|
||||
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,
|
||||
/// crate::key::root::all /
|
||||
Root,
|
||||
/// crate::key::root::ac /!ac{ac}
|
||||
Access,
|
||||
/// crate::key::root::hb /!hb{ts}/{nd}
|
||||
Heartbeat,
|
||||
/// crate::key::root::nd /!nd{nd}
|
||||
|
@ -32,13 +34,15 @@ pub enum KeyCategory {
|
|||
DatabaseIdentifier,
|
||||
/// crate::key::namespace::lg /*{ns}!lg{lg}
|
||||
DatabaseLogAlias,
|
||||
/// crate::key::namespace::tk /*{ns}!tk{tk}
|
||||
NamespaceToken,
|
||||
/// crate::key::namespace::ac /*{ns}!ac{ac}
|
||||
NamespaceAccess,
|
||||
/// crate::key::namespace::us /*{ns}!us{us}
|
||||
NamespaceUser,
|
||||
///
|
||||
/// crate::key::database::all /*{ns}*{db}
|
||||
DatabaseRoot,
|
||||
/// crate::key::database::ac /*{ns}*{db}!ac{ac}
|
||||
DatabaseAccess,
|
||||
/// crate::key::database::az /*{ns}*{db}!az{az}
|
||||
DatabaseAnalyzer,
|
||||
/// crate::key::database::fc /*{ns}*{db}!fn{fc}
|
||||
|
@ -49,14 +53,10 @@ pub enum KeyCategory {
|
|||
DatabaseModel,
|
||||
/// crate::key::database::pa /*{ns}*{db}!pa{pa}
|
||||
DatabaseParameter,
|
||||
/// crate::key::database::sc /*{ns}*{db}!sc{sc}
|
||||
DatabaseScope,
|
||||
/// crate::key::database::tb /*{ns}*{db}!tb{tb}
|
||||
DatabaseTable,
|
||||
/// crate::key::database::ti /+{ns id}*{db id}!ti
|
||||
DatabaseTableIdentifier,
|
||||
/// crate::key::database::tk /*{ns}*{db}!tk{tk}
|
||||
DatabaseToken,
|
||||
/// crate::key::database::ts /*{ns}*{db}!ts{ts}
|
||||
DatabaseTimestamp,
|
||||
/// crate::key::database::us /*{ns}*{db}!us{us}
|
||||
|
@ -64,11 +64,6 @@ pub enum KeyCategory {
|
|||
/// crate::key::database::vs /*{ns}*{db}!vs
|
||||
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}
|
||||
TableRoot,
|
||||
/// crate::key::table::ev /*{ns}*{db}*{tb}!ev{ev}
|
||||
|
@ -124,6 +119,7 @@ impl Display for KeyCategory {
|
|||
let name = match self {
|
||||
KeyCategory::Unknown => "Unknown",
|
||||
KeyCategory::Root => "Root",
|
||||
KeyCategory::Access => "Access",
|
||||
KeyCategory::Heartbeat => "Heartbeat",
|
||||
KeyCategory::Node => "Node",
|
||||
KeyCategory::NamespaceIdentifier => "NamespaceIdentifier",
|
||||
|
@ -135,23 +131,20 @@ impl Display for KeyCategory {
|
|||
KeyCategory::DatabaseAlias => "DatabaseAlias",
|
||||
KeyCategory::DatabaseIdentifier => "DatabaseIdentifier",
|
||||
KeyCategory::DatabaseLogAlias => "DatabaseLogAlias",
|
||||
KeyCategory::NamespaceToken => "NamespaceToken",
|
||||
KeyCategory::NamespaceAccess => "NamespaceAccess",
|
||||
KeyCategory::NamespaceUser => "NamespaceUser",
|
||||
KeyCategory::DatabaseRoot => "DatabaseRoot",
|
||||
KeyCategory::DatabaseAccess => "DatabaseAccess",
|
||||
KeyCategory::DatabaseAnalyzer => "DatabaseAnalyzer",
|
||||
KeyCategory::DatabaseFunction => "DatabaseFunction",
|
||||
KeyCategory::DatabaseLog => "DatabaseLog",
|
||||
KeyCategory::DatabaseModel => "DatabaseModel",
|
||||
KeyCategory::DatabaseParameter => "DatabaseParameter",
|
||||
KeyCategory::DatabaseScope => "DatabaseScope",
|
||||
KeyCategory::DatabaseTable => "DatabaseTable",
|
||||
KeyCategory::DatabaseTableIdentifier => "DatabaseTableIdentifier",
|
||||
KeyCategory::DatabaseToken => "DatabaseToken",
|
||||
KeyCategory::DatabaseTimestamp => "DatabaseTimestamp",
|
||||
KeyCategory::DatabaseUser => "DatabaseUser",
|
||||
KeyCategory::DatabaseVersionstamp => "DatabaseVersionstamp",
|
||||
KeyCategory::ScopeRoot => "ScopeRoot",
|
||||
KeyCategory::ScopeToken => "ScopeToken",
|
||||
KeyCategory::TableRoot => "TableRoot",
|
||||
KeyCategory::TableEvent => "TableEvent",
|
||||
KeyCategory::TableField => "TableField",
|
||||
|
|
|
@ -11,28 +11,24 @@
|
|||
/// crate::key::node::lq /${nd}!lq{lq}{ns}{db}
|
||||
///
|
||||
/// crate::key::namespace::all /*{ns}
|
||||
/// crate::key::namespace::ac /*{ns}!ac{ac}
|
||||
/// crate::key::namespace::db /*{ns}!db{db}
|
||||
/// crate::key::namespace::di /+{ns id}!di
|
||||
/// crate::key::namespace::lg /*{ns}!lg{lg}
|
||||
/// crate::key::namespace::tk /*{ns}!tk{tk}
|
||||
/// crate::key::namespace::us /*{ns}!us{us}
|
||||
///
|
||||
/// 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::fc /*{ns}*{db}!fn{fc}
|
||||
/// crate::key::database::lg /*{ns}*{db}!lg{lg}
|
||||
/// 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::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::us /*{ns}*{db}!us{us}
|
||||
/// 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::ev /*{ns}*{db}*{tb}!ev{ev}
|
||||
/// crate::key::table::fd /*{ns}*{db}*{tb}!fd{fd}
|
||||
|
@ -70,6 +66,5 @@ pub(crate) mod key_req;
|
|||
pub mod namespace;
|
||||
pub mod node;
|
||||
pub mod root;
|
||||
pub mod scope;
|
||||
pub mod table;
|
||||
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::key_req::KeyRequirements;
|
||||
use derive::Key;
|
||||
|
@ -6,48 +6,48 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
||||
#[non_exhaustive]
|
||||
pub struct Tk<'a> {
|
||||
pub struct Ac<'a> {
|
||||
__: u8,
|
||||
_a: u8,
|
||||
pub ns: &'a str,
|
||||
_b: u8,
|
||||
_c: u8,
|
||||
_d: u8,
|
||||
pub tk: &'a str,
|
||||
pub ac: &'a str,
|
||||
}
|
||||
|
||||
pub fn new<'a>(ns: &'a str, tk: &'a str) -> Tk<'a> {
|
||||
Tk::new(ns, tk)
|
||||
pub fn new<'a>(ns: &'a str, ac: &'a str) -> Ac<'a> {
|
||||
Ac::new(ns, ac)
|
||||
}
|
||||
|
||||
pub fn prefix(ns: &str) -> Vec<u8> {
|
||||
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
|
||||
}
|
||||
|
||||
pub fn suffix(ns: &str) -> Vec<u8> {
|
||||
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
|
||||
}
|
||||
|
||||
impl KeyRequirements for Tk<'_> {
|
||||
impl KeyRequirements for Ac<'_> {
|
||||
fn key_category(&self) -> KeyCategory {
|
||||
KeyCategory::NamespaceToken
|
||||
KeyCategory::NamespaceAccess
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Tk<'a> {
|
||||
pub fn new(ns: &'a str, tk: &'a str) -> Self {
|
||||
impl<'a> Ac<'a> {
|
||||
pub fn new(ns: &'a str, ac: &'a str) -> Self {
|
||||
Self {
|
||||
__: b'/',
|
||||
_a: b'*',
|
||||
ns,
|
||||
_b: b'!',
|
||||
_c: b't',
|
||||
_d: b'k',
|
||||
tk,
|
||||
_c: b'a',
|
||||
_d: b'c',
|
||||
ac,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,13 +58,13 @@ mod tests {
|
|||
fn key() {
|
||||
use super::*;
|
||||
#[rustfmt::skip]
|
||||
let val = Tk::new(
|
||||
let val = Ac::new(
|
||||
"testns",
|
||||
"testtk",
|
||||
"testac",
|
||||
);
|
||||
let enc = Tk::encode(&val).unwrap();
|
||||
assert_eq!(enc, b"/*testns\0!tktesttk\0");
|
||||
let dec = Tk::decode(&enc).unwrap();
|
||||
let enc = Ac::encode(&val).unwrap();
|
||||
assert_eq!(enc, b"/*testns\0!actestac\0");
|
||||
let dec = Ac::decode(&enc).unwrap();
|
||||
assert_eq!(val, dec);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
pub mod ac;
|
||||
pub mod all;
|
||||
pub mod db;
|
||||
pub mod di;
|
||||
pub mod tk;
|
||||
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 hb;
|
||||
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::kvs::kv::Key;
|
||||
use crate::sql::statements::DefineAccessStatement;
|
||||
use crate::sql::statements::DefineAnalyzerStatement;
|
||||
use crate::sql::statements::DefineDatabaseStatement;
|
||||
use crate::sql::statements::DefineEventStatement;
|
||||
|
@ -9,9 +10,7 @@ use crate::sql::statements::DefineIndexStatement;
|
|||
use crate::sql::statements::DefineModelStatement;
|
||||
use crate::sql::statements::DefineNamespaceStatement;
|
||||
use crate::sql::statements::DefineParamStatement;
|
||||
use crate::sql::statements::DefineScopeStatement;
|
||||
use crate::sql::statements::DefineTableStatement;
|
||||
use crate::sql::statements::DefineTokenStatement;
|
||||
use crate::sql::statements::DefineUserStatement;
|
||||
use crate::sql::statements::LiveStatement;
|
||||
use std::collections::HashMap;
|
||||
|
@ -31,7 +30,7 @@ pub enum Entry {
|
|||
// Multi definitions
|
||||
Azs(Arc<[DefineAnalyzerStatement]>),
|
||||
Dbs(Arc<[DefineDatabaseStatement]>),
|
||||
Dts(Arc<[DefineTokenStatement]>),
|
||||
Das(Arc<[DefineAccessStatement]>),
|
||||
Dus(Arc<[DefineUserStatement]>),
|
||||
Evs(Arc<[DefineEventStatement]>),
|
||||
Fcs(Arc<[DefineFunctionStatement]>),
|
||||
|
@ -41,11 +40,9 @@ pub enum Entry {
|
|||
Lvs(Arc<[LiveStatement]>),
|
||||
Mls(Arc<[DefineModelStatement]>),
|
||||
Nss(Arc<[DefineNamespaceStatement]>),
|
||||
Nts(Arc<[DefineTokenStatement]>),
|
||||
Nas(Arc<[DefineAccessStatement]>),
|
||||
Nus(Arc<[DefineUserStatement]>),
|
||||
Pas(Arc<[DefineParamStatement]>),
|
||||
Scs(Arc<[DefineScopeStatement]>),
|
||||
Sts(Arc<[DefineTokenStatement]>),
|
||||
Tbs(Arc<[DefineTableStatement]>),
|
||||
// Sequences
|
||||
Seq(U32),
|
||||
|
|
|
@ -1311,7 +1311,7 @@ impl Datastore {
|
|||
/// Evaluates a SQL [`Value`] without checking authenticating config
|
||||
/// This is used in very specific cases, where we do not need to check
|
||||
/// 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.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
|
|
|
@ -214,7 +214,7 @@ mod test_check_lqs_and_send_notifications {
|
|||
use crate::iam::{Auth, Role};
|
||||
use crate::kvs::lq_v2_doc::construct_document;
|
||||
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::{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 {
|
||||
let mut stm = LiveStatement::new(Fields::all());
|
||||
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_SCOPE.to_string(), Value::Strand(Strand::from("scope")));
|
||||
session.insert(OBJ_PATH_TOKEN.to_string(), Value::Strand(Strand::from("token")));
|
||||
let session = Value::Object(Object::from(session));
|
||||
stm.session = Some(session);
|
||||
|
|
|
@ -9,6 +9,7 @@ use futures::lock::Mutex;
|
|||
use uuid::Uuid;
|
||||
|
||||
use sql::permission::Permissions;
|
||||
use sql::statements::DefineAccessStatement;
|
||||
use sql::statements::DefineAnalyzerStatement;
|
||||
use sql::statements::DefineDatabaseStatement;
|
||||
use sql::statements::DefineEventStatement;
|
||||
|
@ -18,9 +19,7 @@ use sql::statements::DefineIndexStatement;
|
|||
use sql::statements::DefineModelStatement;
|
||||
use sql::statements::DefineNamespaceStatement;
|
||||
use sql::statements::DefineParamStatement;
|
||||
use sql::statements::DefineScopeStatement;
|
||||
use sql::statements::DefineTableStatement;
|
||||
use sql::statements::DefineTokenStatement;
|
||||
use sql::statements::DefineUserStatement;
|
||||
use sql::statements::LiveStatement;
|
||||
|
||||
|
@ -1442,21 +1441,24 @@ impl Transaction {
|
|||
})
|
||||
}
|
||||
|
||||
/// Retrieve all namespace token definitions for a specific namespace.
|
||||
pub async fn all_ns_tokens(&mut self, ns: &str) -> Result<Arc<[DefineTokenStatement]>, Error> {
|
||||
let key = crate::key::namespace::tk::prefix(ns);
|
||||
/// Retrieve all namespace access method definitions.
|
||||
pub async fn all_ns_accesses(
|
||||
&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) {
|
||||
if let Entry::Nts(v) = e {
|
||||
if let Entry::Nas(v) = e {
|
||||
v
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
let beg = crate::key::namespace::tk::prefix(ns);
|
||||
let end = crate::key::namespace::tk::suffix(ns);
|
||||
let beg = crate::key::namespace::ac::prefix(ns);
|
||||
let end = crate::key::namespace::ac::suffix(ns);
|
||||
let val = self.getr(beg..end, u32::MAX).await?;
|
||||
let val = val.convert().into();
|
||||
self.cache.set(key, Entry::Nts(Arc::clone(&val)));
|
||||
self.cache.set(key, Entry::Nas(Arc::clone(&val)));
|
||||
val
|
||||
})
|
||||
}
|
||||
|
@ -1503,25 +1505,25 @@ impl Transaction {
|
|||
})
|
||||
}
|
||||
|
||||
/// Retrieve all database token definitions for a specific database.
|
||||
pub async fn all_db_tokens(
|
||||
/// Retrieve all database access method definitions.
|
||||
pub async fn all_db_accesses(
|
||||
&mut self,
|
||||
ns: &str,
|
||||
db: &str,
|
||||
) -> Result<Arc<[DefineTokenStatement]>, Error> {
|
||||
let key = crate::key::database::tk::prefix(ns, db);
|
||||
) -> Result<Arc<[DefineAccessStatement]>, Error> {
|
||||
let key = crate::key::database::ac::prefix(ns, db);
|
||||
Ok(if let Some(e) = self.cache.get(&key) {
|
||||
if let Entry::Dts(v) = e {
|
||||
if let Entry::Das(v) = e {
|
||||
v
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
let beg = crate::key::database::tk::prefix(ns, db);
|
||||
let end = crate::key::database::tk::suffix(ns, db);
|
||||
let beg = crate::key::database::ac::prefix(ns, db);
|
||||
let end = crate::key::database::ac::suffix(ns, db);
|
||||
let val = self.getr(beg..end, u32::MAX).await?;
|
||||
let val = val.convert().into();
|
||||
self.cache.set(key, Entry::Dts(Arc::clone(&val)));
|
||||
self.cache.set(key, Entry::Das(Arc::clone(&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.
|
||||
pub async fn all_tb(
|
||||
&mut self,
|
||||
|
@ -1863,15 +1818,15 @@ impl Transaction {
|
|||
Ok(val.into())
|
||||
}
|
||||
|
||||
/// Retrieve a specific namespace token definition.
|
||||
pub async fn get_ns_token(
|
||||
/// Retrieve a specific namespace access method definition.
|
||||
pub async fn get_ns_access(
|
||||
&mut self,
|
||||
ns: &str,
|
||||
nt: &str,
|
||||
) -> Result<DefineTokenStatement, Error> {
|
||||
let key = crate::key::namespace::tk::new(ns, nt);
|
||||
let val = self.get(key).await?.ok_or(Error::NtNotFound {
|
||||
value: nt.to_owned(),
|
||||
ac: &str,
|
||||
) -> Result<DefineAccessStatement, Error> {
|
||||
let key = crate::key::namespace::ac::new(ns, ac);
|
||||
let val = self.get(key).await?.ok_or(Error::NaNotFound {
|
||||
value: ac.to_owned(),
|
||||
})?;
|
||||
Ok(val.into())
|
||||
}
|
||||
|
@ -1916,16 +1871,16 @@ impl Transaction {
|
|||
Ok(val.into())
|
||||
}
|
||||
|
||||
/// Retrieve a specific database token definition.
|
||||
pub async fn get_db_token(
|
||||
/// Retrieve a specific database access method definition.
|
||||
pub async fn get_db_access(
|
||||
&mut self,
|
||||
ns: &str,
|
||||
db: &str,
|
||||
dt: &str,
|
||||
) -> Result<DefineTokenStatement, Error> {
|
||||
let key = crate::key::database::tk::new(ns, db, dt);
|
||||
let val = self.get(key).await?.ok_or(Error::DtNotFound {
|
||||
value: dt.to_owned(),
|
||||
ac: &str,
|
||||
) -> Result<DefineAccessStatement, Error> {
|
||||
let key = crate::key::database::ac::new(ns, db, ac);
|
||||
let val = self.get(key).await?.ok_or(Error::DaNotFound {
|
||||
value: ac.to_owned(),
|
||||
})?;
|
||||
Ok(val.into())
|
||||
}
|
||||
|
@ -1972,35 +1927,6 @@ impl Transaction {
|
|||
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
|
||||
pub async fn get_lq(
|
||||
&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.
|
||||
pub async fn add_tb(
|
||||
&mut self,
|
||||
|
@ -2525,12 +2421,12 @@ impl Transaction {
|
|||
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() {
|
||||
chn.send(bytes!("-- ------------------------------")).await?;
|
||||
chn.send(bytes!("-- TOKENS")).await?;
|
||||
chn.send(bytes!("-- ACCESSES")).await?;
|
||||
chn.send(bytes!("-- ------------------------------")).await?;
|
||||
chn.send(bytes!("")).await?;
|
||||
for dt in dts.iter() {
|
||||
|
@ -2581,31 +2477,6 @@ impl Transaction {
|
|||
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
|
||||
{
|
||||
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,
|
||||
Rs384,
|
||||
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 {
|
||||
|
@ -47,7 +73,6 @@ impl fmt::Display for Algorithm {
|
|||
Self::Rs256 => "RS256",
|
||||
Self::Rs384 => "RS384",
|
||||
Self::Rs512 => "RS512",
|
||||
Self::Jwks => "JWKS", // Not an algorithm.
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ pub enum Base {
|
|||
Root,
|
||||
Ns,
|
||||
Db,
|
||||
// TODO(gguillemas): This variant is kept in 2.0.0 for backward compatibility. Drop in 3.0.0.
|
||||
Sc(Ident),
|
||||
}
|
||||
|
||||
|
@ -26,6 +27,7 @@ impl fmt::Display for Base {
|
|||
match self {
|
||||
Self::Ns => f.write_str("NAMESPACE"),
|
||||
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::Root => f.write_str("ROOT"),
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! The full type definitions for the SurrealQL query language
|
||||
|
||||
pub(crate) mod access;
|
||||
pub(crate) mod access_type;
|
||||
pub(crate) mod algorithm;
|
||||
#[cfg(feature = "arbitrary")]
|
||||
pub(crate) mod arbitrary;
|
||||
|
@ -74,6 +76,9 @@ pub mod index;
|
|||
pub mod serde;
|
||||
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::array::Array;
|
||||
pub use self::base::Base;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::sql::part::Part;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub const OBJ_PATH_AUTH: &str = "sd";
|
||||
pub const OBJ_PATH_SCOPE: &str = "sc";
|
||||
pub const OBJ_PATH_ACCESS: &str = "ac";
|
||||
pub const OBJ_PATH_AUTH: &str = "rd";
|
||||
pub const OBJ_PATH_TOKEN: &str = "tk";
|
||||
|
||||
pub static ID: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("id")]);
|
||||
|
||||
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 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")]);
|
||||
|
||||
|
|
|
@ -4,27 +4,37 @@ use crate::doc::CursorDoc;
|
|||
use crate::err::Error;
|
||||
use crate::iam::{Action, ResourceKind};
|
||||
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 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)]
|
||||
#[derive(Clone, Default, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
#[non_exhaustive]
|
||||
pub struct DefineTokenStatement {
|
||||
pub struct DefineAccessStatement {
|
||||
pub name: Ident,
|
||||
pub base: Base,
|
||||
pub kind: Algorithm,
|
||||
pub code: String,
|
||||
pub kind: AccessType,
|
||||
pub comment: Option<Strand>,
|
||||
#[revision(start = 2)]
|
||||
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
|
||||
pub(crate) async fn compute(
|
||||
&self,
|
||||
|
@ -41,18 +51,18 @@ impl DefineTokenStatement {
|
|||
let mut run = txn.lock().await;
|
||||
// Clear the cache
|
||||
run.clear_cache();
|
||||
// Check if token already exists
|
||||
if self.if_not_exists && run.get_ns_token(opt.ns(), &self.name).await.is_ok() {
|
||||
return Err(Error::NtAlreadyExists {
|
||||
// Check if access method already exists
|
||||
if self.if_not_exists && run.get_ns_access(opt.ns(), &self.name).await.is_ok() {
|
||||
return Err(Error::AccessNsAlreadyExists {
|
||||
value: self.name.to_string(),
|
||||
});
|
||||
}
|
||||
// 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.set(
|
||||
key,
|
||||
DefineTokenStatement {
|
||||
DefineAccessStatement {
|
||||
if_not_exists: false,
|
||||
..self.clone()
|
||||
},
|
||||
|
@ -66,50 +76,21 @@ impl DefineTokenStatement {
|
|||
let mut run = txn.lock().await;
|
||||
// Clear the cache
|
||||
run.clear_cache();
|
||||
// Check if token already exists
|
||||
// Check if access method already 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(),
|
||||
});
|
||||
}
|
||||
// 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_db(opt.ns(), opt.db(), opt.strict).await?;
|
||||
run.set(
|
||||
key,
|
||||
DefineTokenStatement {
|
||||
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 {
|
||||
DefineAccessStatement {
|
||||
if_not_exists: false,
|
||||
..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 {
|
||||
write!(f, "DEFINE TOKEN",)?;
|
||||
write!(f, "DEFINE ACCESS",)?;
|
||||
if self.if_not_exists {
|
||||
write!(f, " IF NOT EXISTS")?
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
" {} ON {} TYPE {} VALUE {}",
|
||||
self.name,
|
||||
self.base,
|
||||
self.kind,
|
||||
quote_str(&self.code)
|
||||
)?;
|
||||
write!(f, " {} ON {}", self.name, self.base)?;
|
||||
match &self.kind {
|
||||
AccessType::Jwt(ac) => {
|
||||
write!(f, " TYPE JWT {}", ac)?;
|
||||
}
|
||||
AccessType::Record(ac) => {
|
||||
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 {
|
||||
write!(f, " COMMENT {v}")?
|
||||
}
|
||||
|
@ -145,13 +137,12 @@ impl Display for DefineTokenStatement {
|
|||
}
|
||||
}
|
||||
|
||||
impl InfoStructure for DefineTokenStatement {
|
||||
impl InfoStructure for DefineAccessStatement {
|
||||
fn structure(self) -> Value {
|
||||
let Self {
|
||||
name,
|
||||
base,
|
||||
kind,
|
||||
code,
|
||||
comment,
|
||||
..
|
||||
} = self;
|
||||
|
@ -163,8 +154,6 @@ impl InfoStructure for DefineTokenStatement {
|
|||
|
||||
acc.insert("kind".to_string(), kind.structure());
|
||||
|
||||
acc.insert("code".to_string(), code.into());
|
||||
|
||||
if let Some(comment) = comment {
|
||||
acc.insert("comment".to_string(), comment.into());
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod access;
|
||||
mod analyzer;
|
||||
mod database;
|
||||
mod event;
|
||||
|
@ -7,11 +8,10 @@ mod index;
|
|||
mod model;
|
||||
mod namespace;
|
||||
mod param;
|
||||
mod scope;
|
||||
mod table;
|
||||
mod token;
|
||||
mod user;
|
||||
|
||||
pub use access::DefineAccessStatement;
|
||||
pub use analyzer::DefineAnalyzerStatement;
|
||||
pub use database::DefineDatabaseStatement;
|
||||
pub use event::DefineEventStatement;
|
||||
|
@ -21,9 +21,7 @@ pub use index::DefineIndexStatement;
|
|||
pub use model::DefineModelStatement;
|
||||
pub use namespace::DefineNamespaceStatement;
|
||||
pub use param::DefineParamStatement;
|
||||
pub use scope::DefineScopeStatement;
|
||||
pub use table::DefineTableStatement;
|
||||
pub use token::DefineTokenStatement;
|
||||
pub use user::DefineUserStatement;
|
||||
|
||||
use crate::ctx::Context;
|
||||
|
@ -47,8 +45,6 @@ pub enum DefineStatement {
|
|||
Database(DefineDatabaseStatement),
|
||||
Function(DefineFunctionStatement),
|
||||
Analyzer(DefineAnalyzerStatement),
|
||||
Token(DefineTokenStatement),
|
||||
Scope(DefineScopeStatement),
|
||||
Param(DefineParamStatement),
|
||||
Table(DefineTableStatement),
|
||||
Event(DefineEventStatement),
|
||||
|
@ -56,6 +52,7 @@ pub enum DefineStatement {
|
|||
Index(DefineIndexStatement),
|
||||
User(DefineUserStatement),
|
||||
Model(DefineModelStatement),
|
||||
Access(DefineAccessStatement),
|
||||
}
|
||||
|
||||
impl DefineStatement {
|
||||
|
@ -76,8 +73,6 @@ impl DefineStatement {
|
|||
Self::Namespace(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::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::Table(ref v) => v.compute(stk, 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::User(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::Function(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::Table(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::Analyzer(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,
|
||||
#[revision(start = 2)]
|
||||
Db(bool),
|
||||
#[revision(end = 2, convert_fn = "sc_migrate")]
|
||||
Sc(Ident),
|
||||
#[revision(start = 2)]
|
||||
Sc(Ident, bool),
|
||||
#[revision(end = 2, convert_fn = "tb_migrate")]
|
||||
Tb(Ident),
|
||||
#[revision(start = 2)]
|
||||
|
@ -54,10 +50,6 @@ impl InfoStatement {
|
|||
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> {
|
||||
Ok(Self::Tb(n.0, false))
|
||||
}
|
||||
|
@ -122,12 +114,12 @@ impl InfoStatement {
|
|||
tmp.insert(v.name.to_string(), v.to_string().into());
|
||||
}
|
||||
res.insert("users".to_owned(), tmp.into());
|
||||
// Process the tokens
|
||||
// Process the accesses
|
||||
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());
|
||||
}
|
||||
res.insert("tokens".to_owned(), tmp.into());
|
||||
res.insert("accesses".to_owned(), tmp.into());
|
||||
// Ok all good
|
||||
Value::from(res).ok()
|
||||
}
|
||||
|
@ -144,12 +136,6 @@ impl InfoStatement {
|
|||
tmp.insert(v.name.to_string(), v.to_string().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
|
||||
let mut tmp = Object::default();
|
||||
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());
|
||||
}
|
||||
res.insert("params".to_owned(), tmp.into());
|
||||
// Process the scopes
|
||||
// Process the accesses
|
||||
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());
|
||||
}
|
||||
res.insert("scopes".to_owned(), tmp.into());
|
||||
res.insert("accesses".to_owned(), tmp.into());
|
||||
// Process the tables
|
||||
let mut tmp = Object::default();
|
||||
for v in run.all_tb(opt.ns(), opt.db()).await?.iter() {
|
||||
|
@ -189,22 +175,6 @@ impl InfoStatement {
|
|||
// Ok all good
|
||||
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) => {
|
||||
// Allowed to run?
|
||||
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?));
|
||||
// Process the users
|
||||
res.insert("users".to_owned(), process_arr(run.all_ns_users(opt.ns()).await?));
|
||||
// Process the tokens
|
||||
res.insert("tokens".to_owned(), process_arr(run.all_ns_tokens(opt.ns()).await?));
|
||||
// Process the accesses
|
||||
res.insert(
|
||||
"accesses".to_owned(),
|
||||
process_arr(run.all_ns_accesses(opt.ns()).await?),
|
||||
);
|
||||
// Ok all good
|
||||
Value::from(res).ok()
|
||||
}
|
||||
|
@ -304,10 +277,10 @@ impl InfoStatement {
|
|||
"users".to_owned(),
|
||||
process_arr(run.all_db_users(opt.ns(), opt.db()).await?),
|
||||
);
|
||||
// Process the tokens
|
||||
// Process the accesses
|
||||
res.insert(
|
||||
"tokens".to_owned(),
|
||||
process_arr(run.all_db_tokens(opt.ns(), opt.db()).await?),
|
||||
"accesses".to_owned(),
|
||||
process_arr(run.all_db_accesses(opt.ns(), opt.db()).await?),
|
||||
);
|
||||
// Process the functions
|
||||
res.insert(
|
||||
|
@ -324,8 +297,11 @@ impl InfoStatement {
|
|||
"params".to_owned(),
|
||||
process_arr(run.all_db_params(opt.ns(), opt.db()).await?),
|
||||
);
|
||||
// Process the scopes
|
||||
res.insert("scopes".to_owned(), process_arr(run.all_sc(opt.ns(), opt.db()).await?));
|
||||
// Process the accesses
|
||||
res.insert(
|
||||
"accesses".to_owned(),
|
||||
process_arr(run.all_db_accesses(opt.ns(), opt.db()).await?),
|
||||
);
|
||||
// Process the tables
|
||||
res.insert("tables".to_owned(), process_arr(run.all_tb(opt.ns(), opt.db()).await?));
|
||||
// Process the analyzers
|
||||
|
@ -336,30 +312,6 @@ impl InfoStatement {
|
|||
// Ok all good
|
||||
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) => {
|
||||
// Allowed to run?
|
||||
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::Db(false) => f.write_str("INFO FOR DATABASE"),
|
||||
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, true) => write!(f, "INFO FOR TABLE {t} STRUCTURE"),
|
||||
Self::User(ref u, ref b, false) => match b {
|
||||
|
@ -453,7 +403,6 @@ impl InfoStatement {
|
|||
InfoStatement::Root(_) => InfoStatement::Root(true),
|
||||
InfoStatement::Ns(_) => InfoStatement::Ns(true),
|
||||
InfoStatement::Db(_) => InfoStatement::Db(true),
|
||||
InfoStatement::Sc(s, _) => InfoStatement::Sc(s, true),
|
||||
InfoStatement::Tb(t, _) => InfoStatement::Tb(t, 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::define::{
|
||||
DefineAnalyzerStatement, DefineDatabaseStatement, DefineEventStatement, DefineFieldStatement,
|
||||
DefineFunctionStatement, DefineIndexStatement, DefineModelStatement, DefineNamespaceStatement,
|
||||
DefineParamStatement, DefineScopeStatement, DefineStatement, DefineTableStatement,
|
||||
DefineTokenStatement, DefineUserStatement,
|
||||
DefineAccessStatement, DefineAnalyzerStatement, DefineDatabaseStatement, DefineEventStatement,
|
||||
DefineFieldStatement, DefineFunctionStatement, DefineIndexStatement, DefineModelStatement,
|
||||
DefineNamespaceStatement, DefineParamStatement, DefineStatement, DefineTableStatement,
|
||||
DefineUserStatement,
|
||||
};
|
||||
|
||||
pub use self::remove::{
|
||||
RemoveAnalyzerStatement, RemoveDatabaseStatement, RemoveEventStatement, RemoveFieldStatement,
|
||||
RemoveFunctionStatement, RemoveIndexStatement, RemoveModelStatement, RemoveNamespaceStatement,
|
||||
RemoveParamStatement, RemoveScopeStatement, RemoveStatement, RemoveTableStatement,
|
||||
RemoveTokenStatement, RemoveUserStatement,
|
||||
RemoveAccessStatement, RemoveAnalyzerStatement, RemoveDatabaseStatement, RemoveEventStatement,
|
||||
RemoveFieldStatement, RemoveFunctionStatement, RemoveIndexStatement, RemoveModelStatement,
|
||||
RemoveNamespaceStatement, RemoveParamStatement, RemoveStatement, RemoveTableStatement,
|
||||
RemoveUserStatement,
|
||||
};
|
||||
|
|
|
@ -12,14 +12,14 @@ use std::fmt::{self, Display, Formatter};
|
|||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
#[non_exhaustive]
|
||||
pub struct RemoveTokenStatement {
|
||||
pub struct RemoveAccessStatement {
|
||||
pub name: Ident,
|
||||
pub base: Base,
|
||||
#[revision(start = 2)]
|
||||
pub if_exists: bool,
|
||||
}
|
||||
|
||||
impl RemoveTokenStatement {
|
||||
impl RemoveAccessStatement {
|
||||
/// Process this type returning a computed simple Value
|
||||
pub(crate) async fn compute(
|
||||
&self,
|
||||
|
@ -38,9 +38,9 @@ impl RemoveTokenStatement {
|
|||
// Clear the cache
|
||||
run.clear_cache();
|
||||
// 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
|
||||
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?;
|
||||
// Ok all good
|
||||
Ok(Value::None)
|
||||
|
@ -51,22 +51,9 @@ impl RemoveTokenStatement {
|
|||
// Clear the cache
|
||||
run.clear_cache();
|
||||
// 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
|
||||
let key = crate::key::database::tk::new(opt.ns(), opt.db(), &tk.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);
|
||||
let key = crate::key::database::ac::new(opt.ns(), opt.db(), &ac.name);
|
||||
run.del(key).await?;
|
||||
// Ok all good
|
||||
Ok(Value::None)
|
||||
|
@ -77,13 +64,10 @@ impl RemoveTokenStatement {
|
|||
.await;
|
||||
match future {
|
||||
Err(e) if self.if_exists => match e {
|
||||
Error::NtNotFound {
|
||||
Error::NaNotFound {
|
||||
..
|
||||
} => Ok(Value::None),
|
||||
Error::DtNotFound {
|
||||
..
|
||||
} => Ok(Value::None),
|
||||
Error::StNotFound {
|
||||
Error::DaNotFound {
|
||||
..
|
||||
} => Ok(Value::None),
|
||||
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 {
|
||||
write!(f, "REMOVE TOKEN")?;
|
||||
write!(f, "REMOVE ACCESS")?;
|
||||
if self.if_exists {
|
||||
write!(f, " IF EXISTS")?
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod access;
|
||||
mod analyzer;
|
||||
mod database;
|
||||
mod event;
|
||||
|
@ -7,11 +8,10 @@ mod index;
|
|||
mod model;
|
||||
mod namespace;
|
||||
mod param;
|
||||
mod scope;
|
||||
mod table;
|
||||
mod token;
|
||||
mod user;
|
||||
|
||||
pub use access::RemoveAccessStatement;
|
||||
pub use analyzer::RemoveAnalyzerStatement;
|
||||
pub use database::RemoveDatabaseStatement;
|
||||
pub use event::RemoveEventStatement;
|
||||
|
@ -21,9 +21,7 @@ pub use index::RemoveIndexStatement;
|
|||
pub use model::RemoveModelStatement;
|
||||
pub use namespace::RemoveNamespaceStatement;
|
||||
pub use param::RemoveParamStatement;
|
||||
pub use scope::RemoveScopeStatement;
|
||||
pub use table::RemoveTableStatement;
|
||||
pub use token::RemoveTokenStatement;
|
||||
pub use user::RemoveUserStatement;
|
||||
|
||||
use crate::ctx::Context;
|
||||
|
@ -45,8 +43,7 @@ pub enum RemoveStatement {
|
|||
Database(RemoveDatabaseStatement),
|
||||
Function(RemoveFunctionStatement),
|
||||
Analyzer(RemoveAnalyzerStatement),
|
||||
Token(RemoveTokenStatement),
|
||||
Scope(RemoveScopeStatement),
|
||||
Access(RemoveAccessStatement),
|
||||
Param(RemoveParamStatement),
|
||||
Table(RemoveTableStatement),
|
||||
Event(RemoveEventStatement),
|
||||
|
@ -73,8 +70,7 @@ impl RemoveStatement {
|
|||
Self::Namespace(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::Token(ref v) => v.compute(ctx, opt, txn).await,
|
||||
Self::Scope(ref v) => v.compute(ctx, opt, txn).await,
|
||||
Self::Access(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::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::Database(v) => Display::fmt(v, f),
|
||||
Self::Function(v) => Display::fmt(v, f),
|
||||
Self::Token(v) => Display::fmt(v, f),
|
||||
Self::Scope(v) => Display::fmt(v, f),
|
||||
Self::Access(v) => Display::fmt(v, f),
|
||||
Self::Param(v) => Display::fmt(v, f),
|
||||
Self::Table(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 base;
|
||||
mod block;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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::Algorithm;
|
||||
use crate::sql::Base;
|
||||
use crate::sql::Ident;
|
||||
use crate::sql::Strand;
|
||||
|
@ -14,18 +14,18 @@ use serde::ser::Serialize;
|
|||
pub struct Serializer;
|
||||
|
||||
impl ser::Serializer for Serializer {
|
||||
type Ok = DefineTokenStatement;
|
||||
type Ok = DefineAccessStatement;
|
||||
type Error = Error;
|
||||
|
||||
type SerializeSeq = Impossible<DefineTokenStatement, Error>;
|
||||
type SerializeTuple = Impossible<DefineTokenStatement, Error>;
|
||||
type SerializeTupleStruct = Impossible<DefineTokenStatement, Error>;
|
||||
type SerializeTupleVariant = Impossible<DefineTokenStatement, Error>;
|
||||
type SerializeMap = Impossible<DefineTokenStatement, Error>;
|
||||
type SerializeStruct = SerializeDefineTokenStatement;
|
||||
type SerializeStructVariant = Impossible<DefineTokenStatement, Error>;
|
||||
type SerializeSeq = Impossible<DefineAccessStatement, Error>;
|
||||
type SerializeTuple = Impossible<DefineAccessStatement, Error>;
|
||||
type SerializeTupleStruct = Impossible<DefineAccessStatement, Error>;
|
||||
type SerializeTupleVariant = Impossible<DefineAccessStatement, Error>;
|
||||
type SerializeMap = Impossible<DefineAccessStatement, Error>;
|
||||
type SerializeStruct = SerializeDefineAccessStatement;
|
||||
type SerializeStructVariant = Impossible<DefineAccessStatement, Error>;
|
||||
|
||||
const EXPECTED: &'static str = "a struct `DefineTokenStatement`";
|
||||
const EXPECTED: &'static str = "a struct `DefineAccessStatement`";
|
||||
|
||||
#[inline]
|
||||
fn serialize_struct(
|
||||
|
@ -33,23 +33,22 @@ impl ser::Serializer for Serializer {
|
|||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStruct, Error> {
|
||||
Ok(SerializeDefineTokenStatement::default())
|
||||
Ok(SerializeDefineAccessStatement::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct SerializeDefineTokenStatement {
|
||||
pub struct SerializeDefineAccessStatement {
|
||||
name: Ident,
|
||||
base: Base,
|
||||
kind: Algorithm,
|
||||
code: String,
|
||||
kind: AccessType,
|
||||
comment: Option<Strand>,
|
||||
if_not_exists: bool,
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeStruct for SerializeDefineTokenStatement {
|
||||
type Ok = DefineTokenStatement;
|
||||
impl serde::ser::SerializeStruct for SerializeDefineAccessStatement {
|
||||
type Ok = DefineAccessStatement;
|
||||
type Error = 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())?;
|
||||
}
|
||||
"kind" => {
|
||||
self.kind = value.serialize(ser::algorithm::Serializer.wrap())?;
|
||||
}
|
||||
"code" => {
|
||||
self.code = value.serialize(ser::string::Serializer.wrap())?;
|
||||
self.kind = value.serialize(ser::access_type::Serializer.wrap())?;
|
||||
}
|
||||
"comment" => {
|
||||
self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?;
|
||||
|
@ -77,7 +73,7 @@ impl serde::ser::SerializeStruct for SerializeDefineTokenStatement {
|
|||
}
|
||||
key => {
|
||||
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> {
|
||||
Ok(DefineTokenStatement {
|
||||
Ok(DefineAccessStatement {
|
||||
name: self.name,
|
||||
base: self.base,
|
||||
kind: self.kind,
|
||||
code: self.code,
|
||||
comment: self.comment,
|
||||
if_not_exists: self.if_not_exists,
|
||||
})
|
||||
|
@ -102,8 +97,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn default() {
|
||||
let stmt = DefineTokenStatement::default();
|
||||
let value: DefineTokenStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||
let stmt = DefineAccessStatement::default();
|
||||
let value: DefineAccessStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||
assert_eq!(value, stmt);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod access;
|
||||
mod analyzer;
|
||||
mod database;
|
||||
mod event;
|
||||
|
@ -6,9 +7,7 @@ mod function;
|
|||
mod index;
|
||||
mod namespace;
|
||||
mod param;
|
||||
mod scope;
|
||||
mod table;
|
||||
mod token;
|
||||
mod user;
|
||||
|
||||
use crate::err::Error;
|
||||
|
@ -59,8 +58,7 @@ impl ser::Serializer for Serializer {
|
|||
"Analyzer" => {
|
||||
Ok(DefineStatement::Analyzer(value.serialize(analyzer::Serializer.wrap())?))
|
||||
}
|
||||
"Token" => Ok(DefineStatement::Token(value.serialize(token::Serializer.wrap())?)),
|
||||
"Scope" => Ok(DefineStatement::Scope(value.serialize(scope::Serializer.wrap())?)),
|
||||
"Access" => Ok(DefineStatement::Access(value.serialize(access::Serializer.wrap())?)),
|
||||
"Param" => Ok(DefineStatement::Param(value.serialize(param::Serializer.wrap())?)),
|
||||
"Table" => Ok(DefineStatement::Table(value.serialize(table::Serializer.wrap())?)),
|
||||
"Event" => Ok(DefineStatement::Event(value.serialize(event::Serializer.wrap())?)),
|
||||
|
@ -108,15 +106,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn token() {
|
||||
let stmt = DefineStatement::Token(Default::default());
|
||||
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
||||
assert_eq!(stmt, serialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope() {
|
||||
let stmt = DefineStatement::Scope(Default::default());
|
||||
fn access() {
|
||||
let stmt = DefineStatement::Access(Default::default());
|
||||
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
||||
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,
|
||||
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
||||
match variant {
|
||||
"Sc" => Ok(SerializeInfoStatement::with(Which::Sc)),
|
||||
"Tb" => Ok(SerializeInfoStatement::with(Which::Tb)),
|
||||
"User" => Ok(SerializeInfoStatement::with(Which::User)),
|
||||
variant => Err(Error::custom(format!("unexpected tuple variant `{name}::{variant}`"))),
|
||||
|
@ -71,7 +70,6 @@ impl ser::Serializer for Serializer {
|
|||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Which {
|
||||
Sc,
|
||||
Tb,
|
||||
User,
|
||||
}
|
||||
|
@ -79,9 +77,6 @@ enum Which {
|
|||
impl Display for Which {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Which::Sc => {
|
||||
write!(f, "Sc")
|
||||
}
|
||||
Which::Tb => {
|
||||
write!(f, "Tb")
|
||||
}
|
||||
|
@ -121,7 +116,7 @@ impl serde::ser::SerializeTupleVariant for SerializeInfoStatement {
|
|||
(_, 0) => {
|
||||
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())?;
|
||||
}
|
||||
(User, 1) => {
|
||||
|
@ -144,8 +139,6 @@ impl serde::ser::SerializeTupleVariant for SerializeInfoStatement {
|
|||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
use Which::*;
|
||||
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, None) => Err(Error::custom("`InfoStatement::Tb` missing required value(s)")),
|
||||
(User, Some(ident)) => Ok(InfoStatement::User(ident, self.tuple.1, self.tuple.2)),
|
||||
|
@ -179,13 +172,6 @@ mod tests {
|
|||
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]
|
||||
fn tb() {
|
||||
let stmt = InfoStatement::Tb(Default::default(), Default::default());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::err::Error;
|
||||
use crate::sql::statements::RemoveTokenStatement;
|
||||
use crate::sql::statements::RemoveAccessStatement;
|
||||
use crate::sql::value::serde::ser;
|
||||
use crate::sql::Base;
|
||||
use crate::sql::Ident;
|
||||
|
@ -12,18 +12,18 @@ use serde::ser::Serialize;
|
|||
pub struct Serializer;
|
||||
|
||||
impl ser::Serializer for Serializer {
|
||||
type Ok = RemoveTokenStatement;
|
||||
type Ok = RemoveAccessStatement;
|
||||
type Error = Error;
|
||||
|
||||
type SerializeSeq = Impossible<RemoveTokenStatement, Error>;
|
||||
type SerializeTuple = Impossible<RemoveTokenStatement, Error>;
|
||||
type SerializeTupleStruct = Impossible<RemoveTokenStatement, Error>;
|
||||
type SerializeTupleVariant = Impossible<RemoveTokenStatement, Error>;
|
||||
type SerializeMap = Impossible<RemoveTokenStatement, Error>;
|
||||
type SerializeStruct = SerializeRemoveTokenStatement;
|
||||
type SerializeStructVariant = Impossible<RemoveTokenStatement, Error>;
|
||||
type SerializeSeq = Impossible<RemoveAccessStatement, Error>;
|
||||
type SerializeTuple = Impossible<RemoveAccessStatement, Error>;
|
||||
type SerializeTupleStruct = Impossible<RemoveAccessStatement, Error>;
|
||||
type SerializeTupleVariant = Impossible<RemoveAccessStatement, Error>;
|
||||
type SerializeMap = Impossible<RemoveAccessStatement, Error>;
|
||||
type SerializeStruct = SerializeRemoveAccessStatement;
|
||||
type SerializeStructVariant = Impossible<RemoveAccessStatement, Error>;
|
||||
|
||||
const EXPECTED: &'static str = "a struct `RemoveTokenStatement`";
|
||||
const EXPECTED: &'static str = "a struct `RemoveAccessStatement`";
|
||||
|
||||
#[inline]
|
||||
fn serialize_struct(
|
||||
|
@ -31,20 +31,20 @@ impl ser::Serializer for Serializer {
|
|||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStruct, Error> {
|
||||
Ok(SerializeRemoveTokenStatement::default())
|
||||
Ok(SerializeRemoveAccessStatement::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct SerializeRemoveTokenStatement {
|
||||
pub struct SerializeRemoveAccessStatement {
|
||||
name: Ident,
|
||||
base: Base,
|
||||
if_exists: bool,
|
||||
}
|
||||
|
||||
impl serde::ser::SerializeStruct for SerializeRemoveTokenStatement {
|
||||
type Ok = RemoveTokenStatement;
|
||||
impl serde::ser::SerializeStruct for SerializeRemoveAccessStatement {
|
||||
type Ok = RemoveAccessStatement;
|
||||
type Error = 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 => {
|
||||
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> {
|
||||
Ok(RemoveTokenStatement {
|
||||
Ok(RemoveAccessStatement {
|
||||
name: self.name,
|
||||
base: self.base,
|
||||
if_exists: self.if_exists,
|
||||
|
@ -85,8 +85,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn default() {
|
||||
let stmt = RemoveTokenStatement::default();
|
||||
let value: RemoveTokenStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||
let stmt = RemoveAccessStatement::default();
|
||||
let value: RemoveAccessStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||
assert_eq!(value, stmt);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod access;
|
||||
mod analyzer;
|
||||
mod database;
|
||||
mod event;
|
||||
|
@ -6,9 +7,7 @@ mod function;
|
|||
mod index;
|
||||
mod namespace;
|
||||
mod param;
|
||||
mod scope;
|
||||
mod table;
|
||||
mod token;
|
||||
mod user;
|
||||
|
||||
use crate::err::Error;
|
||||
|
@ -59,8 +58,7 @@ impl ser::Serializer for Serializer {
|
|||
"Analyzer" => {
|
||||
Ok(RemoveStatement::Analyzer(value.serialize(analyzer::Serializer.wrap())?))
|
||||
}
|
||||
"Token" => Ok(RemoveStatement::Token(value.serialize(token::Serializer.wrap())?)),
|
||||
"Scope" => Ok(RemoveStatement::Scope(value.serialize(scope::Serializer.wrap())?)),
|
||||
"Access" => Ok(RemoveStatement::Access(value.serialize(access::Serializer.wrap())?)),
|
||||
"Param" => Ok(RemoveStatement::Param(value.serialize(param::Serializer.wrap())?)),
|
||||
"Table" => Ok(RemoveStatement::Table(value.serialize(table::Serializer.wrap())?)),
|
||||
"Event" => Ok(RemoveStatement::Event(value.serialize(event::Serializer.wrap())?)),
|
||||
|
@ -108,15 +106,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn token() {
|
||||
let stmt = RemoveStatement::Token(Default::default());
|
||||
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
||||
assert_eq!(stmt, serialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope() {
|
||||
let stmt = RemoveStatement::Scope(Default::default());
|
||||
fn access() {
|
||||
let stmt = RemoveStatement::Access(Default::default());
|
||||
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
||||
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,
|
||||
pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map! {
|
||||
// Keywords
|
||||
UniCase::ascii("ACCESS") => TokenKind::Keyword(Keyword::Access),
|
||||
UniCase::ascii("AFTER") => TokenKind::Keyword(Keyword::After),
|
||||
UniCase::ascii("ALGORITHM") => TokenKind::Keyword(Keyword::Algorithm),
|
||||
UniCase::ascii("ALL") => TokenKind::Keyword(Keyword::All),
|
||||
UniCase::ascii("ANALYZE") => TokenKind::Keyword(Keyword::Analyze),
|
||||
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("IF") => TokenKind::Keyword(Keyword::If),
|
||||
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("KEEP_PRUNED_CONNECTIONS") => TokenKind::Keyword(Keyword::KeepPrunedConnections),
|
||||
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("UPDATE") => TokenKind::Keyword(Keyword::Update),
|
||||
UniCase::ascii("UPPERCASE") => TokenKind::Keyword(Keyword::Uppercase),
|
||||
UniCase::ascii("URL") => TokenKind::Keyword(Keyword::Url),
|
||||
UniCase::ascii("USE") => TokenKind::Keyword(Keyword::Use),
|
||||
UniCase::ascii("USER") => TokenKind::Keyword(Keyword::User),
|
||||
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("RS384") => TokenKind::Algorithm(Algorithm::Rs384),
|
||||
UniCase::ascii("RS512") => TokenKind::Algorithm(Algorithm::Rs512),
|
||||
UniCase::ascii("JWKS") => jwks_token_kind(), // Necessary because `phf_map!` doesn't support `cfg` attributes
|
||||
|
||||
// Distance
|
||||
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
|
||||
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::ns") => PathKind::Function,
|
||||
UniCase::ascii("session::origin") => PathKind::Function,
|
||||
UniCase::ascii("session::sc") => PathKind::Function,
|
||||
UniCase::ascii("session::sd") => PathKind::Function,
|
||||
UniCase::ascii("session::ac") => PathKind::Function,
|
||||
UniCase::ascii("session::rd") => PathKind::Function,
|
||||
UniCase::ascii("session::token") => PathKind::Function,
|
||||
//
|
||||
UniCase::ascii("string::concat") => PathKind::Function,
|
||||
|
|
|
@ -1,26 +1,30 @@
|
|||
use reblessive::Stk;
|
||||
|
||||
use crate::sql::access_type::JwtAccessVerify;
|
||||
use crate::sql::index::HnswParams;
|
||||
use crate::{
|
||||
sql::{
|
||||
access_type,
|
||||
base::Base,
|
||||
filter::Filter,
|
||||
index::{Distance, VectorType},
|
||||
statements::{
|
||||
DefineAnalyzerStatement, DefineDatabaseStatement, DefineEventStatement,
|
||||
DefineFieldStatement, DefineFunctionStatement, DefineIndexStatement,
|
||||
DefineNamespaceStatement, DefineParamStatement, DefineScopeStatement, DefineStatement,
|
||||
DefineTableStatement, DefineTokenStatement, DefineUserStatement,
|
||||
DefineAccessStatement, DefineAnalyzerStatement, DefineDatabaseStatement,
|
||||
DefineEventStatement, DefineFieldStatement, DefineFunctionStatement,
|
||||
DefineIndexStatement, DefineNamespaceStatement, DefineParamStatement, DefineStatement,
|
||||
DefineTableStatement, DefineUserStatement,
|
||||
},
|
||||
table_type,
|
||||
tokenizer::Tokenizer,
|
||||
Ident, Idioms, Index, Kind, Param, Permissions, Scoring, Strand, TableType, Values,
|
||||
AccessType, Ident, Idioms, Index, Kind, Param, Permissions, Scoring, Strand, TableType,
|
||||
Values,
|
||||
},
|
||||
syn::{
|
||||
parser::{
|
||||
mac::{expected, unexpected},
|
||||
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!("FUNCTION") => self.parse_define_function(ctx).await.map(DefineStatement::Function),
|
||||
t!("USER") => self.parse_define_user().map(DefineStatement::User),
|
||||
t!("TOKEN") => self.parse_define_token().map(DefineStatement::Token),
|
||||
t!("SCOPE") => self.parse_define_scope(ctx).await.map(DefineStatement::Scope),
|
||||
t!("TOKEN") => self.parse_define_token().map(DefineStatement::Access),
|
||||
t!("SCOPE") => self.parse_define_scope(ctx).await.map(DefineStatement::Access),
|
||||
t!("PARAM") => self.parse_define_param(ctx).await.map(DefineStatement::Param),
|
||||
t!("TABLE") => self.parse_define_table(ctx).await.map(DefineStatement::Table),
|
||||
t!("EVENT") => {
|
||||
|
@ -43,6 +47,7 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("INDEX") => self.parse_define_index().map(DefineStatement::Index),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +216,10 @@ impl Parser<'_> {
|
|||
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")) {
|
||||
expected!(self, t!("NOT"));
|
||||
expected!(self, t!("EXISTS"));
|
||||
|
@ -221,9 +229,10 @@ impl Parser<'_> {
|
|||
};
|
||||
let name = self.next_token_value()?;
|
||||
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,
|
||||
base,
|
||||
if_not_exists,
|
||||
|
@ -236,17 +245,49 @@ impl Parser<'_> {
|
|||
self.pop_peek();
|
||||
res.comment = Some(self.next_token_value()?);
|
||||
}
|
||||
t!("VALUE") => {
|
||||
self.pop_peek();
|
||||
res.code = self.next_token_value::<Strand>()?.0;
|
||||
}
|
||||
t!("TYPE") => {
|
||||
self.pop_peek();
|
||||
match self.next().kind {
|
||||
TokenKind::Algorithm(x) => {
|
||||
res.kind = x;
|
||||
match self.peek_kind() {
|
||||
t!("JWT") => {
|
||||
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,
|
||||
|
@ -256,7 +297,8 @@ impl Parser<'_> {
|
|||
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")) {
|
||||
expected!(self, t!("NOT"));
|
||||
expected!(self, t!("EXISTS"));
|
||||
|
@ -265,13 +307,130 @@ impl Parser<'_> {
|
|||
false
|
||||
};
|
||||
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,
|
||||
code: DefineScopeStatement::random_code(),
|
||||
base: base.clone(),
|
||||
if_not_exists,
|
||||
..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 {
|
||||
match self.peek_kind() {
|
||||
t!("COMMENT") => {
|
||||
|
@ -280,20 +439,26 @@ impl Parser<'_> {
|
|||
}
|
||||
t!("SESSION") => {
|
||||
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") => {
|
||||
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") => {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
res.kind = AccessType::Record(ac);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
@ -914,4 +1079,110 @@ impl Parser<'_> {
|
|||
}
|
||||
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!("NAMESPACE") => InfoStatement::Ns(false),
|
||||
t!("DATABASE") => InfoStatement::Db(false),
|
||||
t!("SCOPE") => {
|
||||
let ident = self.next_token_value()?;
|
||||
InfoStatement::Sc(ident, false)
|
||||
}
|
||||
t!("TABLE") => {
|
||||
let ident = self.next_token_value()?;
|
||||
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
|
||||
///
|
||||
/// 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
|
||||
/// Expects the next keyword to be a base.
|
||||
|
@ -327,9 +328,9 @@ impl Parser<'_> {
|
|||
}
|
||||
x => {
|
||||
if scope_allowed {
|
||||
unexpected!(self, x, "'NAMEPSPACE', 'DATABASE', 'ROOT', 'SCOPE' or 'KV'")
|
||||
unexpected!(self, x, "'NAMEPSPACE', 'DATABASE', 'ROOT' or 'SCOPE'")
|
||||
} else {
|
||||
unexpected!(self, x, "'NAMEPSPACE', 'DATABASE', 'ROOT', or 'KV'")
|
||||
unexpected!(self, x, "'NAMEPSPACE', 'DATABASE' or 'ROOT'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::{
|
||||
sql::{
|
||||
statements::{
|
||||
remove::RemoveAnalyzerStatement, RemoveDatabaseStatement, RemoveEventStatement,
|
||||
RemoveFieldStatement, RemoveFunctionStatement, RemoveIndexStatement,
|
||||
RemoveNamespaceStatement, RemoveParamStatement, RemoveScopeStatement, RemoveStatement,
|
||||
remove::RemoveAnalyzerStatement, RemoveAccessStatement, RemoveDatabaseStatement,
|
||||
RemoveEventStatement, RemoveFieldStatement, RemoveFunctionStatement,
|
||||
RemoveIndexStatement, RemoveNamespaceStatement, RemoveParamStatement, RemoveStatement,
|
||||
RemoveUserStatement,
|
||||
},
|
||||
Param,
|
||||
|
@ -66,7 +66,7 @@ impl Parser<'_> {
|
|||
if_exists,
|
||||
})
|
||||
}
|
||||
t!("TOKEN") => {
|
||||
t!("ACCESS") => {
|
||||
let if_exists = if self.eat(t!("IF")) {
|
||||
expected!(self, t!("EXISTS"));
|
||||
true
|
||||
|
@ -75,28 +75,14 @@ impl Parser<'_> {
|
|||
};
|
||||
let name = self.next_token_value()?;
|
||||
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,
|
||||
base,
|
||||
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") => {
|
||||
let if_exists = if self.eat(t!("IF")) {
|
||||
expected!(self, t!("EXISTS"));
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use crate::{
|
||||
sql::{
|
||||
access_type::{
|
||||
AccessType, JwtAccess, JwtAccessIssue, JwtAccessVerify, JwtAccessVerifyJwks,
|
||||
JwtAccessVerifyKey, RecordAccess,
|
||||
},
|
||||
block::Entry,
|
||||
changefeed::ChangeFeed,
|
||||
filter::Filter,
|
||||
|
@ -8,15 +12,15 @@ use crate::{
|
|||
statements::{
|
||||
analyze::AnalyzeStatement, show::ShowSince, show::ShowStatement, sleep::SleepStatement,
|
||||
BeginStatement, BreakStatement, CancelStatement, CommitStatement, ContinueStatement,
|
||||
CreateStatement, DefineAnalyzerStatement, DefineDatabaseStatement,
|
||||
DefineEventStatement, DefineFieldStatement, DefineFunctionStatement,
|
||||
DefineIndexStatement, DefineNamespaceStatement, DefineParamStatement, DefineStatement,
|
||||
DefineTableStatement, DefineTokenStatement, DeleteStatement, ForeachStatement,
|
||||
IfelseStatement, InfoStatement, InsertStatement, KillStatement, OptionStatement,
|
||||
OutputStatement, RelateStatement, RemoveAnalyzerStatement, RemoveDatabaseStatement,
|
||||
RemoveEventStatement, RemoveFieldStatement, RemoveFunctionStatement,
|
||||
RemoveIndexStatement, RemoveNamespaceStatement, RemoveParamStatement,
|
||||
RemoveScopeStatement, RemoveStatement, RemoveTableStatement, RemoveTokenStatement,
|
||||
CreateStatement, DefineAccessStatement, DefineAnalyzerStatement,
|
||||
DefineDatabaseStatement, DefineEventStatement, DefineFieldStatement,
|
||||
DefineFunctionStatement, DefineIndexStatement, DefineNamespaceStatement,
|
||||
DefineParamStatement, DefineStatement, DefineTableStatement, DeleteStatement,
|
||||
ForeachStatement, IfelseStatement, InfoStatement, InsertStatement, KillStatement,
|
||||
OptionStatement, OutputStatement, RelateStatement, RemoveAccessStatement,
|
||||
RemoveAnalyzerStatement, RemoveDatabaseStatement, RemoveEventStatement,
|
||||
RemoveFieldStatement, RemoveFunctionStatement, RemoveIndexStatement,
|
||||
RemoveNamespaceStatement, RemoveParamStatement, RemoveStatement, RemoveTableStatement,
|
||||
RemoveUserStatement, SelectStatement, SetStatement, ThrowStatement, UpdateStatement,
|
||||
UseStatement,
|
||||
},
|
||||
|
@ -219,26 +223,132 @@ fn parse_define_user() {
|
|||
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]
|
||||
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!(
|
||||
parse_stmt,
|
||||
r#"DEFINE TOKEN a ON SCOPE b TYPE EDDSA VALUE "foo" 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::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!(
|
||||
res,
|
||||
Statement::Define(DefineStatement::Token(DefineTokenStatement {
|
||||
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||
name: Ident("a".to_string()),
|
||||
base: Base::Sc(Ident("b".to_string())),
|
||||
kind: Algorithm::EdDSA,
|
||||
code: "foo".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,
|
||||
}))
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
// 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]
|
||||
fn parse_define_scope() {
|
||||
let res = test_parse!(
|
||||
|
@ -247,16 +357,568 @@ fn parse_define_scope() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
// manually compare since DefineScopeStatement creates a random code in its parser.
|
||||
let Statement::Define(DefineStatement::Scope(stmt)) = res else {
|
||||
// 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.session, Some(Duration(std::time::Duration::from_secs(1))));
|
||||
assert_eq!(stmt.signup, Some(Value::Bool(true)));
|
||||
assert_eq!(stmt.signin, Some(Value::Bool(false)));
|
||||
assert_eq!(stmt.if_not_exists, false);
|
||||
match stmt.kind {
|
||||
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]
|
||||
|
@ -683,9 +1345,6 @@ fn parse_info() {
|
|||
let res = test_parse!(parse_stmt, "INFO FOR NS").unwrap();
|
||||
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();
|
||||
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!(
|
||||
res,
|
||||
Statement::Remove(RemoveStatement::Token(RemoveTokenStatement {
|
||||
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 {
|
||||
Statement::Remove(RemoveStatement::Access(RemoveAccessStatement {
|
||||
name: Ident("foo".to_owned()),
|
||||
base: Base::Db,
|
||||
if_exists: false,
|
||||
}))
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{
|
||||
sql::{
|
||||
access_type::{AccessType, JwtAccess, JwtAccessVerify, JwtAccessVerifyKey, RecordAccess},
|
||||
block::Entry,
|
||||
changefeed::ChangeFeed,
|
||||
filter::Filter,
|
||||
|
@ -8,13 +9,13 @@ use crate::{
|
|||
statements::{
|
||||
analyze::AnalyzeStatement, show::ShowSince, show::ShowStatement, sleep::SleepStatement,
|
||||
BeginStatement, BreakStatement, CancelStatement, CommitStatement, ContinueStatement,
|
||||
CreateStatement, DefineAnalyzerStatement, DefineDatabaseStatement,
|
||||
DefineEventStatement, DefineFieldStatement, DefineFunctionStatement,
|
||||
DefineIndexStatement, DefineNamespaceStatement, DefineParamStatement, DefineStatement,
|
||||
DefineTableStatement, DefineTokenStatement, DeleteStatement, ForeachStatement,
|
||||
IfelseStatement, InfoStatement, InsertStatement, KillStatement, OutputStatement,
|
||||
RelateStatement, RemoveFieldStatement, RemoveFunctionStatement, RemoveStatement,
|
||||
SelectStatement, SetStatement, ThrowStatement, UpdateStatement,
|
||||
CreateStatement, DefineAccessStatement, DefineAnalyzerStatement,
|
||||
DefineDatabaseStatement, DefineEventStatement, DefineFieldStatement,
|
||||
DefineFunctionStatement, DefineIndexStatement, DefineNamespaceStatement,
|
||||
DefineParamStatement, DefineStatement, DefineTableStatement, DeleteStatement,
|
||||
ForeachStatement, IfelseStatement, InfoStatement, InsertStatement, KillStatement,
|
||||
OutputStatement, RelateStatement, RemoveFieldStatement, RemoveFunctionStatement,
|
||||
RemoveStatement, SelectStatement, SetStatement, ThrowStatement, UpdateStatement,
|
||||
},
|
||||
tokenizer::Tokenizer,
|
||||
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>) {
|
||||
RETURN a
|
||||
} 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 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;
|
||||
|
@ -73,7 +74,6 @@ static SOURCE: &str = r#"
|
|||
IF foo { bar } ELSE IF faz { baz } ELSE { baq };
|
||||
INFO FOR ROOT;
|
||||
INFO FOR NAMESPACE;
|
||||
INFO FOR SCOPE scope;
|
||||
INFO FOR USER user ON namespace;
|
||||
SELECT bar as foo,[1,2],bar OMIT bar FROM ONLY a,1
|
||||
WITH INDEX index,index_2
|
||||
|
@ -191,11 +191,21 @@ fn statements() -> Vec<Statement> {
|
|||
permissions: Permission::Full,
|
||||
if_not_exists: false,
|
||||
})),
|
||||
Statement::Define(DefineStatement::Token(DefineTokenStatement {
|
||||
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||
name: Ident("a".to_string()),
|
||||
base: Base::Sc(Ident("b".to_string())),
|
||||
kind: Algorithm::EdDSA,
|
||||
code: "foo".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,
|
||||
})),
|
||||
|
@ -435,7 +445,6 @@ fn statements() -> Vec<Statement> {
|
|||
}),
|
||||
Statement::Info(InfoStatement::Root(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::Select(SelectStatement {
|
||||
expr: Fields(
|
||||
|
|
|
@ -24,7 +24,9 @@ macro_rules! keyword {
|
|||
}
|
||||
|
||||
keyword! {
|
||||
Access => "ACCESS",
|
||||
After => "AFTER",
|
||||
Algorithm => "ALGORITHM",
|
||||
All => "ALL",
|
||||
Analyze => "ANALYZE",
|
||||
Analyzer => "ANALYZER",
|
||||
|
@ -93,6 +95,9 @@ keyword! {
|
|||
Into => "INTO",
|
||||
If => "IF",
|
||||
Is => "IS",
|
||||
Issuer => "ISSUER",
|
||||
Jwt => "JWT",
|
||||
Jwks => "JWKS",
|
||||
Key => "KEY",
|
||||
KeepPrunedConnections => "KEEP_PRUNED_CONNECTIONS",
|
||||
Kill => "KILL",
|
||||
|
@ -169,6 +174,7 @@ keyword! {
|
|||
Unset => "UNSET",
|
||||
Update => "UPDATE",
|
||||
Uppercase => "UPPERCASE",
|
||||
Url => "URL",
|
||||
Use => "USE",
|
||||
User => "USER",
|
||||
Value => "VALUE",
|
||||
|
|
|
@ -365,8 +365,8 @@ impl TokenKind {
|
|||
)
|
||||
}
|
||||
|
||||
fn algorithm_as_str(algo: Algorithm) -> &'static str {
|
||||
match algo {
|
||||
fn algorithm_as_str(alg: Algorithm) -> &'static str {
|
||||
match alg {
|
||||
Algorithm::EdDSA => "EDDSA",
|
||||
Algorithm::Es256 => "ES256",
|
||||
Algorithm::Es384 => "ES384",
|
||||
|
@ -380,7 +380,6 @@ impl TokenKind {
|
|||
Algorithm::Rs256 => "RS256",
|
||||
Algorithm::Rs384 => "RS384",
|
||||
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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.2.0"
|
||||
|
@ -531,6 +525,33 @@ dependencies = [
|
|||
"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]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
|
@ -732,9 +753,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "echodb"
|
||||
version = "0.4.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "312221c0bb46e82cd250c818404ef9dce769a4d5a62915c0249b577762eec34a"
|
||||
checksum = "1ac31e38aeac770dd01b9d6c9ab2a6d7f025815f71105911cf6de073a5db8ee1"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"imbl",
|
||||
|
@ -1063,6 +1084,16 @@ version = "0.28.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "hash32"
|
||||
version = "0.2.1"
|
||||
|
@ -1622,6 +1653,12 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "path-clean"
|
||||
version = "1.0.1"
|
||||
|
@ -1685,6 +1722,40 @@ dependencies = [
|
|||
"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]]
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
|
@ -1694,6 +1765,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
|
@ -1927,14 +2008,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "reblessive"
|
||||
version = "0.3.2"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb88832db7cfbac349e0276a84d4fbbcdb9cfe8069affbc765d8fd012265ba45"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
]
|
||||
checksum = "4149deda5bd21e0f6ccaa2f907cd542541521dead5861bc51bebdf2af4acaf2a"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
|
@ -2019,9 +2095,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "revision"
|
||||
version = "0.5.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87eb86913082f8976b06d07a59f17df9120e6f38b882cf3fc5a45b4499e224b6"
|
||||
checksum = "588784c1d9453cfd2ce1b7aff06c903513677cf0e63779a0a3085ee8a44f5b17"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
|
@ -2037,9 +2113,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "revision-derive"
|
||||
version = "0.5.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf996fc5f61f1dbec35799b5c00c6dda12e8862e8cb782ed24e10d0292e60ed3"
|
||||
checksum = "854ff0b6794d4e0aab5e4486870941caefe9f258e63cad2f21b49a6302377c85"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro-error",
|
||||
|
@ -2107,6 +2183,27 @@ dependencies = [
|
|||
"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]]
|
||||
name = "roaring"
|
||||
version = "0.10.3"
|
||||
|
@ -2522,7 +2619,7 @@ dependencies = [
|
|||
"new_debug_unreachable",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"phf_shared",
|
||||
"phf_shared 0.10.0",
|
||||
"precomputed-hash",
|
||||
]
|
||||
|
||||
|
@ -2554,6 +2651,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"path-clean",
|
||||
"pharos",
|
||||
"reblessive",
|
||||
"revision",
|
||||
"ring 0.17.8",
|
||||
"rust_decimal",
|
||||
|
@ -2588,6 +2686,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"cedar-policy",
|
||||
"chrono",
|
||||
"ciborium",
|
||||
"deunicode",
|
||||
"dmp",
|
||||
"echodb",
|
||||
|
@ -2607,6 +2706,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"pbkdf2",
|
||||
"pharos",
|
||||
"phf",
|
||||
"pin-project-lite",
|
||||
"quick_cache",
|
||||
"radix_trie",
|
||||
|
@ -2616,6 +2716,7 @@ dependencies = [
|
|||
"regex-syntax",
|
||||
"revision",
|
||||
"ring 0.17.8",
|
||||
"rmpv",
|
||||
"roaring",
|
||||
"rust-stemmers",
|
||||
"rust_decimal",
|
||||
|
@ -2634,6 +2735,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"trice",
|
||||
"ulid",
|
||||
"unicase",
|
||||
"url",
|
||||
"uuid",
|
||||
"wasm-bindgen-futures",
|
||||
|
@ -2918,6 +3020,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"ACCESS"
|
||||
"AFTER"
|
||||
"ALGORITHM"
|
||||
"ALL"
|
||||
"ALLINSIDE"
|
||||
"AND"
|
||||
|
@ -61,6 +63,9 @@
|
|||
"INTERSECTS"
|
||||
"INTO"
|
||||
"IS"
|
||||
"JWKS"
|
||||
"JWT"
|
||||
"KEY"
|
||||
"KILL"
|
||||
"KV"
|
||||
"LET"
|
||||
|
@ -89,15 +94,14 @@
|
|||
"PATH"
|
||||
"PERMISSIONS"
|
||||
"PI"
|
||||
"RECORD"
|
||||
"RELATE"
|
||||
"REMOVE"
|
||||
"REPLACE"
|
||||
"RETURN"
|
||||
"SC"
|
||||
"SCHEMAFUL"
|
||||
"SCHEMAFULL"
|
||||
"SCHEMALESS"
|
||||
"SCOPE"
|
||||
"SELECT"
|
||||
"SESSION"
|
||||
"SET"
|
||||
|
@ -116,14 +120,13 @@
|
|||
"TB"
|
||||
"THEN"
|
||||
"TIMEOUT"
|
||||
"TK"
|
||||
"TOKEN"
|
||||
"TRANSACTION"
|
||||
"TRUE"
|
||||
"TYPE"
|
||||
"UNIQUE"
|
||||
"UPDATE"
|
||||
"UPPERCASE"
|
||||
"URL"
|
||||
"USE"
|
||||
"USER"
|
||||
"VALUE"
|
||||
|
@ -277,12 +280,12 @@
|
|||
"search::offsets("
|
||||
"session"
|
||||
"session::"
|
||||
"session::ac("
|
||||
"session::db("
|
||||
"session::id("
|
||||
"session::ip("
|
||||
"session::ns("
|
||||
"session::origin("
|
||||
"session::sc"
|
||||
# Sleep is just going to slow the fuzzer down
|
||||
# "sleep("
|
||||
"string"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"AFTER"
|
||||
"ALGORITHM"
|
||||
"ALL"
|
||||
"ALLINSIDE"
|
||||
"AND"
|
||||
|
@ -61,6 +62,9 @@
|
|||
"INTERSECTS"
|
||||
"INTO"
|
||||
"IS"
|
||||
"JWKS"
|
||||
"JWT"
|
||||
"KEY"
|
||||
"KILL"
|
||||
"KV"
|
||||
"LET"
|
||||
|
@ -89,15 +93,14 @@
|
|||
"PATH"
|
||||
"PERMISSIONS"
|
||||
"PI"
|
||||
"RECORD"
|
||||
"RELATE"
|
||||
"REMOVE"
|
||||
"REPLACE"
|
||||
"RETURN"
|
||||
"SC"
|
||||
"SCHEMAFUL"
|
||||
"SCHEMAFULL"
|
||||
"SCHEMALESS"
|
||||
"SCOPE"
|
||||
"SELECT"
|
||||
"SESSION"
|
||||
"SET"
|
||||
|
@ -116,14 +119,13 @@
|
|||
"TB"
|
||||
"THEN"
|
||||
"TIMEOUT"
|
||||
"TK"
|
||||
"TOKEN"
|
||||
"TRANSACTION"
|
||||
"TRUE"
|
||||
"TYPE"
|
||||
"UNIQUE"
|
||||
"UPDATE"
|
||||
"UPPERCASE"
|
||||
"URL"
|
||||
"USE"
|
||||
"USER"
|
||||
"VALUE"
|
||||
|
@ -277,12 +279,12 @@
|
|||
"search::offsets("
|
||||
"session"
|
||||
"session::"
|
||||
"session::ac("
|
||||
"session::db("
|
||||
"session::id("
|
||||
"session::ip("
|
||||
"session::ns("
|
||||
"session::origin("
|
||||
"session::sc"
|
||||
"sleep("
|
||||
"string"
|
||||
"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
|
||||
///
|
||||
|
@ -401,7 +401,7 @@ where
|
|||
/// use serde::Serialize;
|
||||
/// use surrealdb::sql;
|
||||
/// use surrealdb::opt::auth::Root;
|
||||
/// use surrealdb::opt::auth::Scope;
|
||||
/// use surrealdb::opt::auth::Record;
|
||||
///
|
||||
/// #[derive(Debug, Serialize)]
|
||||
/// struct AuthParams {
|
||||
|
@ -423,19 +423,19 @@ where
|
|||
/// // Select the namespace/database to use
|
||||
/// db.use_ns("namespace").use_db("database").await?;
|
||||
///
|
||||
/// // Define the scope
|
||||
/// // Define the user record access
|
||||
/// 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) )
|
||||
/// SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password) )
|
||||
/// "#;
|
||||
/// db.query(sql).await?.check()?;
|
||||
///
|
||||
/// // Sign a user up
|
||||
/// db.signup(Scope {
|
||||
/// db.signup(Record {
|
||||
/// namespace: "namespace",
|
||||
/// database: "database",
|
||||
/// scope: "user_scope",
|
||||
/// access: "user_access",
|
||||
/// params: AuthParams {
|
||||
/// email: "john.doe@example.com".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
|
||||
///
|
||||
|
@ -530,12 +530,12 @@ where
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Scope signin
|
||||
/// Record signin
|
||||
///
|
||||
/// ```no_run
|
||||
/// use serde::Serialize;
|
||||
/// use surrealdb::opt::auth::Root;
|
||||
/// use surrealdb::opt::auth::Scope;
|
||||
/// use surrealdb::opt::auth::Record;
|
||||
///
|
||||
/// #[derive(Debug, Serialize)]
|
||||
/// struct AuthParams {
|
||||
|
@ -551,10 +551,10 @@ where
|
|||
/// db.use_ns("namespace").use_db("database").await?;
|
||||
///
|
||||
/// // Sign a user in
|
||||
/// db.signin(Scope {
|
||||
/// db.signin(Record {
|
||||
/// namespace: "namespace",
|
||||
/// database: "database",
|
||||
/// scope: "user_scope",
|
||||
/// access: "user_access",
|
||||
/// params: AuthParams {
|
||||
/// email: "john.doe@example.com".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::Jwt;
|
||||
use crate::api::opt::auth::Namespace;
|
||||
use crate::api::opt::auth::Record;
|
||||
use crate::api::opt::auth::Root;
|
||||
use crate::api::opt::auth::Scope;
|
||||
use crate::api::opt::PatchOp;
|
||||
use crate::api::Response as QueryResponse;
|
||||
use crate::api::Surreal;
|
||||
|
@ -42,10 +42,10 @@ async fn api() {
|
|||
|
||||
// signup
|
||||
let _: Jwt = DB
|
||||
.signup(Scope {
|
||||
.signup(Record {
|
||||
namespace: "test-ns",
|
||||
database: "test-db",
|
||||
scope: "scope",
|
||||
access: "access",
|
||||
params: AuthParams {},
|
||||
})
|
||||
.await
|
||||
|
@ -77,10 +77,10 @@ async fn api() {
|
|||
.await
|
||||
.unwrap();
|
||||
let _: Jwt = DB
|
||||
.signin(Scope {
|
||||
.signin(Record {
|
||||
namespace: "test-ns",
|
||||
database: "test-db",
|
||||
scope: "scope",
|
||||
access: "access",
|
||||
params: AuthParams {},
|
||||
})
|
||||
.await
|
||||
|
|
|
@ -63,24 +63,24 @@ pub struct Database<'a> {
|
|||
|
||||
impl Credentials<Signin, Jwt> for Database<'_> {}
|
||||
|
||||
/// Credentials for the scope user
|
||||
/// Credentials for the record user
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Scope<'a, P> {
|
||||
pub struct Record<'a, P> {
|
||||
/// The namespace the user has access to
|
||||
#[serde(rename = "ns")]
|
||||
pub namespace: &'a str,
|
||||
/// The database the user has access to
|
||||
#[serde(rename = "db")]
|
||||
pub database: &'a str,
|
||||
/// The scope to use for signin and signup
|
||||
#[serde(rename = "sc")]
|
||||
pub scope: &'a str,
|
||||
/// The access method to use for signin and signup
|
||||
#[serde(rename = "ac")]
|
||||
pub access: &'a str,
|
||||
/// The additional params to use
|
||||
#[serde(flatten)]
|
||||
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.
|
||||
///
|
||||
|
|
|
@ -18,8 +18,8 @@ mod api_integration {
|
|||
use surrealdb::opt::auth::Database;
|
||||
use surrealdb::opt::auth::Jwt;
|
||||
use surrealdb::opt::auth::Namespace;
|
||||
use surrealdb::opt::auth::Record as RecordAccess;
|
||||
use surrealdb::opt::auth::Root;
|
||||
use surrealdb::opt::auth::Scope;
|
||||
use surrealdb::opt::Config;
|
||||
use surrealdb::opt::PatchOp;
|
||||
use surrealdb::opt::Resource;
|
||||
|
|
|
@ -52,14 +52,14 @@ async fn invalidate() {
|
|||
}
|
||||
|
||||
#[test_log::test(tokio::test)]
|
||||
async fn signup_scope() {
|
||||
async fn signup_record() {
|
||||
let (permit, db) = new_db().await;
|
||||
let database = Ulid::new().to_string();
|
||||
db.use_ns(NS).use_db(&database).await.unwrap();
|
||||
let scope = Ulid::new().to_string();
|
||||
let access = Ulid::new().to_string();
|
||||
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) )
|
||||
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();
|
||||
drop(permit);
|
||||
response.check().unwrap();
|
||||
db.signup(Scope {
|
||||
db.signup(RecordAccess {
|
||||
namespace: NS,
|
||||
database: &database,
|
||||
scope: &scope,
|
||||
access: &access,
|
||||
params: AuthParams {
|
||||
email: "john.doe@example.com",
|
||||
pass: "password123",
|
||||
|
@ -121,16 +121,16 @@ async fn signin_db() {
|
|||
}
|
||||
|
||||
#[test_log::test(tokio::test)]
|
||||
async fn signin_scope() {
|
||||
async fn signin_record() {
|
||||
let (permit, db) = new_db().await;
|
||||
let database = Ulid::new().to_string();
|
||||
db.use_ns(NS).use_db(&database).await.unwrap();
|
||||
let scope = Ulid::new().to_string();
|
||||
let email = format!("{scope}@example.com");
|
||||
let access = Ulid::new().to_string();
|
||||
let email = format!("{access}@example.com");
|
||||
let pass = "password123";
|
||||
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) )
|
||||
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();
|
||||
drop(permit);
|
||||
response.check().unwrap();
|
||||
db.signup(Scope {
|
||||
db.signup(RecordAccess {
|
||||
namespace: NS,
|
||||
database: &database,
|
||||
scope: &scope,
|
||||
access: &access,
|
||||
params: AuthParams {
|
||||
pass,
|
||||
email: &email,
|
||||
|
@ -149,10 +149,10 @@ async fn signin_scope() {
|
|||
})
|
||||
.await
|
||||
.unwrap();
|
||||
db.signin(Scope {
|
||||
db.signin(RecordAccess {
|
||||
namespace: NS,
|
||||
database: &database,
|
||||
scope: &scope,
|
||||
access: &access,
|
||||
params: AuthParams {
|
||||
pass,
|
||||
email: &email,
|
||||
|
@ -163,16 +163,16 @@ async fn signin_scope() {
|
|||
}
|
||||
|
||||
#[test_log::test(tokio::test)]
|
||||
async fn scope_throws_error() {
|
||||
async fn record_access_throws_error() {
|
||||
let (permit, db) = new_db().await;
|
||||
let database = Ulid::new().to_string();
|
||||
db.use_ns(NS).use_db(&database).await.unwrap();
|
||||
let scope = Ulid::new().to_string();
|
||||
let email = format!("{scope}@example.com");
|
||||
let access = Ulid::new().to_string();
|
||||
let email = format!("{access}@example.com");
|
||||
let pass = "password123";
|
||||
let sql = format!(
|
||||
"
|
||||
DEFINE SCOPE `{scope}` SESSION 1s
|
||||
DEFINE ACCESS `{access}` ON DB TYPE RECORD DURATION 1s
|
||||
SIGNUP {{ THROW 'signup_thrown_error' }}
|
||||
SIGNIN {{ THROW 'signin_thrown_error' }}
|
||||
"
|
||||
|
@ -182,10 +182,10 @@ async fn scope_throws_error() {
|
|||
response.check().unwrap();
|
||||
|
||||
match db
|
||||
.signup(Scope {
|
||||
.signup(RecordAccess {
|
||||
namespace: NS,
|
||||
database: &database,
|
||||
scope: &scope,
|
||||
access: &access,
|
||||
params: AuthParams {
|
||||
pass,
|
||||
email: &email,
|
||||
|
@ -203,10 +203,10 @@ async fn scope_throws_error() {
|
|||
};
|
||||
|
||||
match db
|
||||
.signin(Scope {
|
||||
.signin(RecordAccess {
|
||||
namespace: NS,
|
||||
database: &database,
|
||||
scope: &scope,
|
||||
access: &access,
|
||||
params: AuthParams {
|
||||
pass,
|
||||
email: &email,
|
||||
|
@ -225,16 +225,16 @@ async fn scope_throws_error() {
|
|||
}
|
||||
|
||||
#[test_log::test(tokio::test)]
|
||||
async fn scope_invalid_query() {
|
||||
async fn record_access_invalid_query() {
|
||||
let (permit, db) = new_db().await;
|
||||
let database = Ulid::new().to_string();
|
||||
db.use_ns(NS).use_db(&database).await.unwrap();
|
||||
let scope = Ulid::new().to_string();
|
||||
let email = format!("{scope}@example.com");
|
||||
let access = Ulid::new().to_string();
|
||||
let email = format!("{access}@example.com");
|
||||
let pass = "password123";
|
||||
let sql = format!(
|
||||
"
|
||||
DEFINE SCOPE `{scope}` SESSION 1s
|
||||
DEFINE ACCESS `{access}` ON DB TYPE RECORD DURATION 1s
|
||||
SIGNUP {{ SELECT * FROM ONLY [1, 2] }}
|
||||
SIGNIN {{ SELECT * FROM ONLY [1, 2] }}
|
||||
"
|
||||
|
@ -244,10 +244,10 @@ async fn scope_invalid_query() {
|
|||
response.check().unwrap();
|
||||
|
||||
match db
|
||||
.signup(Scope {
|
||||
.signup(RecordAccess {
|
||||
namespace: NS,
|
||||
database: &database,
|
||||
scope: &scope,
|
||||
access: &access,
|
||||
params: AuthParams {
|
||||
pass,
|
||||
email: &email,
|
||||
|
@ -255,9 +255,12 @@ async fn scope_invalid_query() {
|
|||
})
|
||||
.await
|
||||
{
|
||||
Err(Error::Db(surrealdb::err::Error::SignupQueryFailed)) => (),
|
||||
Err(Error::Db(surrealdb::err::Error::AccessRecordSignupQueryFailed)) => (),
|
||||
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!(
|
||||
e,
|
||||
|
@ -267,10 +270,10 @@ async fn scope_invalid_query() {
|
|||
};
|
||||
|
||||
match db
|
||||
.signin(Scope {
|
||||
.signin(RecordAccess {
|
||||
namespace: NS,
|
||||
database: &database,
|
||||
scope: &scope,
|
||||
access: &access,
|
||||
params: AuthParams {
|
||||
pass,
|
||||
email: &email,
|
||||
|
@ -278,9 +281,12 @@ async fn scope_invalid_query() {
|
|||
})
|
||||
.await
|
||||
{
|
||||
Err(Error::Db(surrealdb::err::Error::SigninQueryFailed)) => (),
|
||||
Err(Error::Db(surrealdb::err::Error::AccessRecordSigninQueryFailed)) => (),
|
||||
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!(
|
||||
e,
|
||||
|
|
|
@ -236,7 +236,7 @@ async fn create_or_insert_with_permissions() -> Result<(), Error> {
|
|||
CREATE demo SET id = demo:one;
|
||||
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?;
|
||||
assert_eq!(res.len(), 2);
|
||||
//
|
||||
|
|
|
@ -55,8 +55,8 @@ async fn define_statement_database() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
databases: { test: 'DEFINE DATABASE test' },
|
||||
tokens: {},
|
||||
users: {},
|
||||
}",
|
||||
);
|
||||
|
@ -84,12 +84,11 @@ async fn define_statement_function() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: { test: 'DEFINE FUNCTION fn::test($first: string, $last: string) { RETURN $first + $last; } PERMISSIONS FULL' },
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: {},
|
||||
users: {},
|
||||
}",
|
||||
|
@ -116,12 +115,11 @@ async fn define_statement_table_drop() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY DROP SCHEMALESS PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
|
@ -148,12 +146,11 @@ async fn define_statement_table_schemaless() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
|
@ -184,12 +181,11 @@ async fn define_statement_table_schemafull() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
|
@ -216,12 +212,11 @@ async fn define_statement_table_schemaful() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
|
@ -256,12 +251,11 @@ async fn define_statement_table_foreigntable() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: {
|
||||
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',
|
||||
|
@ -289,12 +283,11 @@ async fn define_statement_table_foreigntable() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: {
|
||||
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 val = Value::parse(
|
||||
r#"{
|
||||
accesses: {},
|
||||
analyzers: {
|
||||
autocomplete: 'DEFINE ANALYZER autocomplete FILTERS LOWERCASE,EDGENGRAM(2,10)',
|
||||
english: 'DEFINE ANALYZER english TOKENIZERS BLANK,CLASS FILTERS LOWERCASE,SNOWBALL(ENGLISH)',
|
||||
htmlAnalyzer: 'DEFINE ANALYZER htmlAnalyzer FUNCTION fn::stripHtml TOKENIZERS BLANK,CLASS'
|
||||
},
|
||||
tokens: {},
|
||||
functions: {
|
||||
stripHtml: "DEFINE FUNCTION fn::stripHtml($html: string) { RETURN string::replace($html, /<[^>]*>/, ''); } PERMISSIONS FULL"
|
||||
},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: {},
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ databases: { DB: 'DEFINE DATABASE DB' }, tokens: { }, users: { } }"],
|
||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, databases: { DB: 'DEFINE DATABASE DB' }, users: { } }"],
|
||||
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; } PERMISSIONS FULL\" }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; } PERMISSIONS FULL\" }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
||||
vec!["{ accesses: { }, analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
|
@ -1674,17 +1666,17 @@ async fn permissions_checks_define_analyzer() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_checks_define_token_ns() {
|
||||
async fn permissions_checks_define_access_ns() {
|
||||
let scenario = HashMap::from([
|
||||
("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"),
|
||||
]);
|
||||
|
||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||
let check_results = [
|
||||
vec!["{ databases: { }, tokens: { token: \"DEFINE TOKEN token ON NAMESPACE TYPE HS512 VALUE 'secret'\" }, users: { } }"],
|
||||
vec!["{ databases: { }, tokens: { }, users: { } }"]
|
||||
vec!["{ accesses: { access: \"DEFINE ACCESS access ON NAMESPACE TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, databases: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, databases: { }, users: { } }"]
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
|
@ -1716,17 +1708,17 @@ async fn permissions_checks_define_token_ns() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_checks_define_token_db() {
|
||||
async fn permissions_checks_define_access_db() {
|
||||
let scenario = HashMap::from([
|
||||
("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"),
|
||||
]);
|
||||
|
||||
// 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: { token: \"DEFINE TOKEN token ON DATABASE TYPE HS512 VALUE 'secret'\" }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, 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!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ databases: { }, tokens: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||
vec!["{ databases: { }, tokens: { }, users: { } }"]
|
||||
vec!["{ accesses: { }, databases: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||
vec!["{ accesses: { }, databases: { }, users: { } }"]
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, 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: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
|
@ -1884,28 +1876,28 @@ async fn permissions_checks_define_user_db() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_checks_define_scope() {
|
||||
async fn permissions_checks_define_access_record() {
|
||||
let scenario = HashMap::from([
|
||||
("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"),
|
||||
]);
|
||||
|
||||
// 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: { account: 'DEFINE SCOPE account SESSION 1h' }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, 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!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
// Root level
|
||||
((().into(), Role::Owner), ("NS", "DB"), true),
|
||||
((().into(), Role::Editor), ("NS", "DB"), true),
|
||||
((().into(), Role::Editor), ("NS", "DB"), false),
|
||||
((().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), ("NS", "DB"), false),
|
||||
((("NS",).into(), Role::Editor), ("OTHER_NS", "DB"), false),
|
||||
((("NS",).into(), Role::Viewer), ("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", "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", "DB"), false),
|
||||
((("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),
|
||||
|
@ -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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo' PERMISSIONS FULL\" }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo' PERMISSIONS FULL\" }, tables: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB TYPE ANY SCHEMALESS PERMISSIONS NONE' }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"]
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { TB: 'DEFINE TABLE TB TYPE ANY SCHEMALESS PERMISSIONS NONE' }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
|
@ -2159,17 +2151,16 @@ async fn define_statement_table_permissions() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: {
|
||||
default: 'DEFINE TABLE default TYPE ANY SCHEMALESS PERMISSIONS NONE',
|
||||
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'
|
||||
},
|
||||
tokens: {},
|
||||
users: {}
|
||||
}",
|
||||
);
|
||||
|
@ -2499,10 +2490,10 @@ async fn redefining_existing_param_with_if_not_exists_should_error() -> Result<(
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn redefining_existing_scope_should_not_error() -> Result<(), Error> {
|
||||
async fn redefining_existing_access_should_not_error() -> Result<(), Error> {
|
||||
let sql = "
|
||||
DEFINE SCOPE example;
|
||||
DEFINE SCOPE example;
|
||||
DEFINE ACCESS example ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||
DEFINE ACCESS example ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||
";
|
||||
let dbs = new_ds().await?;
|
||||
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]
|
||||
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 = "
|
||||
DEFINE SCOPE IF NOT EXISTS example;
|
||||
DEFINE SCOPE IF NOT EXISTS example;
|
||||
DEFINE ACCESS IF NOT EXISTS example ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||
DEFINE ACCESS IF NOT EXISTS example ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||
";
|
||||
let dbs = new_ds().await?;
|
||||
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);
|
||||
//
|
||||
let tmp = res.remove(0).result.unwrap_err();
|
||||
assert!(matches!(tmp, Error::ScAlreadyExists { .. }),);
|
||||
assert!(matches!(tmp, Error::AccessDbAlreadyExists { .. }),);
|
||||
//
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2578,46 +2569,6 @@ async fn redefining_existing_table_with_if_not_exists_should_error() -> Result<(
|
|||
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]
|
||||
async fn redefining_existing_user_should_not_error() -> Result<(), Error> {
|
||||
let sql = "
|
||||
|
@ -2831,12 +2782,11 @@ async fn define_table_relation_redefinition_info() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { likes: 'DEFINE TABLE likes TYPE RELATION IN person OUT person SCHEMALESS PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
|
@ -2861,12 +2811,11 @@ async fn define_table_relation_redefinition_info() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { likes: 'DEFINE TABLE likes TYPE RELATION IN person OUT person | thing SCHEMALESS PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
|
@ -2891,12 +2840,11 @@ async fn define_table_relation_redefinition_info() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { likes: 'DEFINE TABLE likes TYPE RELATION IN person OUT person | thing | other SCHEMALESS PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
|
|
|
@ -456,7 +456,7 @@ async fn delete_with_permissions() -> Result<(), Error> {
|
|||
DELETE friends_with:1 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?;
|
||||
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: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?;
|
||||
assert_eq!(res.len(), 2);
|
||||
//
|
||||
|
|
|
@ -24,7 +24,7 @@ pub async fn iam_run_case(
|
|||
sess: &Session,
|
||||
should_succeed: bool,
|
||||
) -> 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();
|
||||
owner_sess.au = Arc::new(Auth::for_root(Role::Owner));
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ async fn info_for_ns() {
|
|||
let sql = r#"
|
||||
DEFINE DATABASE DB;
|
||||
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
|
||||
"#;
|
||||
let dbs = new_ds().await.unwrap();
|
||||
|
@ -52,7 +52,7 @@ async fn info_for_ns() {
|
|||
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
||||
|
||||
let output_regex = Regex::new(
|
||||
r"\{ databases: \{ DB: .* \}, tokens: \{ token: .* \}, users: \{ user: .* \} \}",
|
||||
r"\{ accesses: \{ access: .* \}, databases: \{ DB: .* \}, users: \{ user: .* \} \}",
|
||||
)
|
||||
.unwrap();
|
||||
let out_str = out.unwrap().to_string();
|
||||
|
@ -68,9 +68,9 @@ async fn info_for_ns() {
|
|||
async fn info_for_db() {
|
||||
let sql = r#"
|
||||
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 TOKEN token ON DB TYPE HS512 VALUE 'secret';
|
||||
DEFINE FUNCTION fn::greet() {RETURN "Hello";};
|
||||
DEFINE PARAM $param VALUE "foo";
|
||||
DEFINE ANALYZER analyzer TOKENIZERS BLANK;
|
||||
|
@ -85,33 +85,7 @@ async fn info_for_db() {
|
|||
let out = res.pop().unwrap().output();
|
||||
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 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 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),
|
||||
|
@ -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
|
||||
let check_results = [
|
||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
|
@ -344,45 +318,6 @@ async fn permissions_checks_info_db() {
|
|||
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]
|
||||
async fn permissions_checks_info_table() {
|
||||
let scenario = HashMap::from([
|
||||
|
|
|
@ -26,12 +26,11 @@ async fn define_global_param() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: { test: 'DEFINE PARAM $test VALUE 12345 PERMISSIONS FULL' },
|
||||
scopes: {},
|
||||
tables: {},
|
||||
users: {},
|
||||
}",
|
||||
|
|
|
@ -36,12 +36,11 @@ async fn remove_statement_table() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: {},
|
||||
users: {}
|
||||
}",
|
||||
|
@ -71,12 +70,11 @@ async fn remove_statement_analyzer() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: {},
|
||||
users: {}
|
||||
}",
|
||||
|
@ -418,9 +416,9 @@ async fn should_not_error_when_remove_param_if_exists() -> Result<(), Error> {
|
|||
}
|
||||
|
||||
#[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 = "
|
||||
REMOVE SCOPE foo;
|
||||
REMOVE ACCESS foo ON DB;
|
||||
";
|
||||
let dbs = new_ds().await?;
|
||||
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);
|
||||
//
|
||||
let tmp = res.remove(0).result.unwrap_err();
|
||||
assert!(matches!(tmp, Error::ScNotFound { .. }),);
|
||||
assert!(matches!(tmp, Error::DaNotFound { .. }),);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 = "
|
||||
REMOVE SCOPE IF EXISTS foo;
|
||||
";
|
||||
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;
|
||||
REMOVE ACCESS IF EXISTS foo ON DB;
|
||||
";
|
||||
let dbs = new_ds().await?;
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ databases: { DB: 'DEFINE DATABASE DB' }, tokens: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, databases: { DB: 'DEFINE DATABASE DB' }, users: { } }"],
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; } PERMISSIONS FULL\" }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; } PERMISSIONS FULL\" }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
|
@ -686,17 +652,17 @@ async fn permissions_checks_remove_analyzer() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_checks_remove_ns_token() {
|
||||
async fn permissions_checks_remove_ns_access() {
|
||||
let scenario = HashMap::from([
|
||||
("prepare", "DEFINE TOKEN token ON NS TYPE HS512 VALUE 'secret'"),
|
||||
("test", "REMOVE TOKEN token ON NS"),
|
||||
("prepare", "DEFINE ACCESS access ON NS TYPE JWT ALGORITHM HS512 KEY 'secret'"),
|
||||
("test", "REMOVE ACCESS access ON NS"),
|
||||
("check", "INFO FOR NS"),
|
||||
]);
|
||||
|
||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||
let check_results = [
|
||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ databases: { }, tokens: { token: \"DEFINE TOKEN token ON NAMESPACE TYPE HS512 VALUE 'secret'\" }, users: { } }"],
|
||||
vec!["{ accesses: { }, databases: { }, 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 = [
|
||||
|
@ -728,17 +694,17 @@ async fn permissions_checks_remove_ns_token() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_checks_remove_db_token() {
|
||||
async fn permissions_checks_remove_db_access() {
|
||||
let scenario = HashMap::from([
|
||||
("prepare", "DEFINE TOKEN token ON DB TYPE HS512 VALUE 'secret'"),
|
||||
("test", "REMOVE TOKEN token ON DB"),
|
||||
("prepare", "DEFINE ACCESS access ON DB TYPE JWT ALGORITHM HS512 KEY 'secret'"),
|
||||
("test", "REMOVE ACCESS access ON DB"),
|
||||
("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: { }, tables: { }, tokens: { token: \"DEFINE TOKEN token ON DATABASE TYPE HS512 VALUE 'secret'\" }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, 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 = [
|
||||
|
@ -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
|
||||
let check_results = [
|
||||
vec!["{ databases: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ databases: { }, tokens: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||
vec!["{ accesses: { }, databases: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, databases: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, 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: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"],
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
|
@ -895,48 +861,6 @@ async fn permissions_checks_remove_db_user() {
|
|||
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]
|
||||
async fn permissions_checks_remove_param() {
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo' PERMISSIONS FULL\" }, scopes: { }, tables: { }, tokens: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo' PERMISSIONS FULL\" }, tables: { }, users: { } }"],
|
||||
];
|
||||
|
||||
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
|
||||
let check_results = [
|
||||
vec!["{ analyzers: { }, functions: { }, models: { }, params: { }, scopes: { }, tables: { }, tokens: { }, 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: { }, users: { } }"],
|
||||
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { TB: 'DEFINE TABLE TB TYPE ANY SCHEMALESS PERMISSIONS NONE' }, users: { } }"],
|
||||
];
|
||||
|
||||
let test_cases = [
|
||||
|
|
|
@ -242,8 +242,8 @@ async fn loose_mode_all_ok() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
databases: { test: 'DEFINE DATABASE test' },
|
||||
tokens: {},
|
||||
users: {},
|
||||
}",
|
||||
);
|
||||
|
@ -252,12 +252,11 @@ async fn loose_mode_all_ok() -> Result<(), Error> {
|
|||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
"{
|
||||
accesses: {},
|
||||
analyzers: {},
|
||||
tokens: {},
|
||||
functions: {},
|
||||
models: {},
|
||||
params: {},
|
||||
scopes: {},
|
||||
tables: { test: 'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE' },
|
||||
users: {},
|
||||
}",
|
||||
|
|
|
@ -41,7 +41,7 @@ struct SigninParams<'a> {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
db: Option<&'a str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
sc: Option<&'a str>,
|
||||
ac: Option<&'a str>,
|
||||
}
|
||||
|
||||
enum SocketMsg {
|
||||
|
@ -385,7 +385,7 @@ impl Socket {
|
|||
pass: &str,
|
||||
ns: Option<&str>,
|
||||
db: Option<&str>,
|
||||
sc: Option<&str>,
|
||||
ac: Option<&str>,
|
||||
) -> Result<String> {
|
||||
// Send message and receive response
|
||||
let msg = self
|
||||
|
@ -396,7 +396,7 @@ impl Socket {
|
|||
pass,
|
||||
ns,
|
||||
db,
|
||||
sc
|
||||
ac
|
||||
}]),
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -35,11 +35,11 @@ async fn info() -> Result<(), Box<dyn std::error::Error>> {
|
|||
socket.send_message_use(Some(NS), Some(DB)).await?;
|
||||
// Define a user table
|
||||
socket.send_message_query("DEFINE TABLE user PERMISSIONS FULL").await?;
|
||||
// Define a user scope
|
||||
// Define a user record access method
|
||||
socket
|
||||
.send_message_query(
|
||||
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) )
|
||||
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?;
|
||||
// Sign in as scope user
|
||||
socket.send_message_signin("user", "pass", Some(NS), Some(DB), Some("scope")).await?;
|
||||
// Sign in as record user
|
||||
socket.send_message_signin("user", "pass", Some(NS), Some(DB), Some("user")).await?;
|
||||
// Send INFO command
|
||||
let res = socket.send_request("info", json!([])).await?;
|
||||
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?;
|
||||
// Specify a namespace and database
|
||||
socket.send_message_use(Some(NS), Some(DB)).await?;
|
||||
// Setup the scope
|
||||
// Define a user record access method
|
||||
socket
|
||||
.send_message_query(
|
||||
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) )
|
||||
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!([{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"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?;
|
||||
// Specify a namespace and database
|
||||
socket.send_message_use(Some(NS), Some(DB)).await?;
|
||||
// Setup the scope
|
||||
// Define a user record access method
|
||||
socket
|
||||
.send_message_query(
|
||||
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) )
|
||||
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,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"pass": "pass",
|
||||
}]
|
||||
|
@ -170,7 +170,7 @@ async fn signin() -> Result<(), Box<dyn std::error::Error>> {
|
|||
[{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"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?;
|
||||
// Specify a namespace and database
|
||||
socket_permanent.send_message_use(Some(NS), Some(DB)).await?;
|
||||
// Setup the scope
|
||||
// Define a user record access method
|
||||
socket_permanent
|
||||
.send_message_query(
|
||||
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) )
|
||||
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!([{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"pass": "pass",
|
||||
}]),
|
||||
|
@ -933,23 +933,23 @@ async fn session_expiration() {
|
|||
socket.send_message_signin(USER, PASS, None, None, None).await.unwrap();
|
||||
// Specify a namespace and database
|
||||
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
||||
// Setup the scope
|
||||
// Define a user record access method
|
||||
socket
|
||||
.send_message_query(
|
||||
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) )
|
||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||
;"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
// Create resource that requires a scope session to query
|
||||
// Create resource that requires a session with the access method to query
|
||||
socket
|
||||
.send_message_query(
|
||||
r#"
|
||||
DEFINE TABLE test SCHEMALESS
|
||||
PERMISSIONS FOR select, create, update, delete WHERE $scope = "scope"
|
||||
PERMISSIONS FOR select, create, update, delete WHERE $access = "user"
|
||||
;"#,
|
||||
)
|
||||
.await
|
||||
|
@ -970,7 +970,7 @@ async fn session_expiration() {
|
|||
[{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"pass": "pass",
|
||||
}]
|
||||
|
@ -1012,7 +1012,7 @@ async fn session_expiration() {
|
|||
[{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"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();
|
||||
// Specify a namespace and database
|
||||
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
||||
// Setup the scope
|
||||
// Define a user record access method
|
||||
socket
|
||||
.send_message_query(
|
||||
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) )
|
||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||
;"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
// Create resource that requires a scope session to query
|
||||
// Create resource that requires a session with the access method to query
|
||||
socket
|
||||
.send_message_query(
|
||||
r#"
|
||||
DEFINE TABLE test SCHEMALESS
|
||||
PERMISSIONS FOR select, create, update, delete WHERE $scope = "scope"
|
||||
PERMISSIONS FOR select, create, update, delete WHERE $access = "user"
|
||||
;"#,
|
||||
)
|
||||
.await
|
||||
|
@ -1080,7 +1080,7 @@ async fn session_expiration_operations() {
|
|||
[{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"pass": "pass",
|
||||
}]
|
||||
|
@ -1217,7 +1217,7 @@ async fn session_expiration_operations() {
|
|||
json!([{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "another@email.com",
|
||||
"pass": "pass",
|
||||
}]),
|
||||
|
@ -1248,7 +1248,7 @@ async fn session_expiration_operations() {
|
|||
[{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "another@email.com",
|
||||
"pass": "pass",
|
||||
}]
|
||||
|
@ -1299,23 +1299,23 @@ async fn session_reauthentication() {
|
|||
socket.send_message_query("INFO FOR ROOT").await.unwrap();
|
||||
// Specify a namespace and database
|
||||
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
||||
// Setup the scope
|
||||
// Define a user record access method
|
||||
socket
|
||||
.send_message_query(
|
||||
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) )
|
||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||
;"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
// Create resource that requires a scope session to query
|
||||
// Create resource that requires a session with the access method to query
|
||||
socket
|
||||
.send_message_query(
|
||||
r#"
|
||||
DEFINE TABLE test SCHEMALESS
|
||||
PERMISSIONS FOR select, create, update, delete WHERE $scope = "scope"
|
||||
PERMISSIONS FOR select, create, update, delete WHERE $access = "user"
|
||||
;"#,
|
||||
)
|
||||
.await
|
||||
|
@ -1336,7 +1336,7 @@ async fn session_reauthentication() {
|
|||
[{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"pass": "pass",
|
||||
}]
|
||||
|
@ -1353,7 +1353,7 @@ async fn session_reauthentication() {
|
|||
assert!(res["result"].is_string(), "result: {:?}", res);
|
||||
let res = res["result"].as_str().unwrap();
|
||||
assert!(res.starts_with("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9"), "result: {}", res);
|
||||
// Authenticate using the scope token
|
||||
// Authenticate using the token
|
||||
socket.send_request("authenticate", json!([res,])).await.unwrap();
|
||||
// Check that we do not have root access
|
||||
let res = socket.send_message_query("INFO FOR ROOT").await.unwrap();
|
||||
|
@ -1363,7 +1363,7 @@ async fn session_reauthentication() {
|
|||
"result: {:?}",
|
||||
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();
|
||||
assert_eq!(res[0]["result"], json!(["yes"]), "result: {:?}", res);
|
||||
// Authenticate using the root token
|
||||
|
@ -1387,23 +1387,23 @@ async fn session_reauthentication_expired() {
|
|||
socket.send_message_query("INFO FOR ROOT").await.unwrap();
|
||||
// Specify a namespace and database
|
||||
socket.send_message_use(Some(NS), Some(DB)).await.unwrap();
|
||||
// Setup the scope
|
||||
// Define a user record access method
|
||||
socket
|
||||
.send_message_query(
|
||||
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) )
|
||||
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
|
||||
;"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
// Create resource that requires a scope session to query
|
||||
// Create resource that requires a session with the access method to query
|
||||
socket
|
||||
.send_message_query(
|
||||
r#"
|
||||
DEFINE TABLE test SCHEMALESS
|
||||
PERMISSIONS FOR select, create, update, delete WHERE $scope = "scope"
|
||||
PERMISSIONS FOR select, create, update, delete WHERE $access = "user"
|
||||
;"#,
|
||||
)
|
||||
.await
|
||||
|
@ -1424,7 +1424,7 @@ async fn session_reauthentication_expired() {
|
|||
[{
|
||||
"ns": NS,
|
||||
"db": DB,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"pass": "pass",
|
||||
}]
|
||||
|
@ -1441,7 +1441,7 @@ async fn session_reauthentication_expired() {
|
|||
assert!(res["result"].is_string(), "result: {:?}", res);
|
||||
let res = res["result"].as_str().unwrap();
|
||||
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();
|
||||
// Wait two seconds for token to expire
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||
|
|
|
@ -1160,14 +1160,14 @@ mod http_integration {
|
|||
.default_headers(headers)
|
||||
.build()?;
|
||||
|
||||
// Create a scope
|
||||
// Define a record access method
|
||||
{
|
||||
let res = client
|
||||
.post(format!("http://{addr}/sql"))
|
||||
.basic_auth(USER, Some(PASS))
|
||||
.body(
|
||||
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) )
|
||||
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?);
|
||||
}
|
||||
|
||||
// Signup into the scope
|
||||
// Signup using the defined record access method
|
||||
{
|
||||
let req_body = serde_json::to_string(
|
||||
json!({
|
||||
"ns": ns,
|
||||
"db": db,
|
||||
"sc": "scope",
|
||||
"ac": "user",
|
||||
"email": "email@email.com",
|
||||
"pass": "pass",
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue