Allow defining JWT access at the root level (#4348)
This commit is contained in:
parent
fc154142fa
commit
e281a4e41e
14 changed files with 469 additions and 44 deletions
|
@ -298,12 +298,6 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested namespace access method does not exist
|
|
||||||
#[error("The namespace access method '{value}' does not exist")]
|
|
||||||
NaNotFound {
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The requested namespace login does not exist
|
/// The requested namespace login does not exist
|
||||||
#[error("The namespace login '{value}' does not exist")]
|
#[error("The namespace login '{value}' does not exist")]
|
||||||
NlNotFound {
|
NlNotFound {
|
||||||
|
@ -316,12 +310,6 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested database access method does not exist
|
|
||||||
#[error("The database access method '{value}' does not exist")]
|
|
||||||
DaNotFound {
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The requested database login does not exist
|
/// The requested database login does not exist
|
||||||
#[error("The database login '{value}' does not exist")]
|
#[error("The database login '{value}' does not exist")]
|
||||||
DlNotFound {
|
DlNotFound {
|
||||||
|
@ -948,15 +936,18 @@ pub enum Error {
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested namespace access method already exists
|
/// The requested namespace access method already exists
|
||||||
#[error("The namespace access method '{value}' already exists")]
|
#[error("The access method '{value}' already exists in the namespace '{ns}'")]
|
||||||
AccessNsAlreadyExists {
|
AccessNsAlreadyExists {
|
||||||
value: String,
|
value: String,
|
||||||
|
ns: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested database access method already exists
|
/// The requested database access method already exists
|
||||||
#[error("The database access method '{value}' already exists")]
|
#[error("The access method '{value}' already exists in the database '{db}'")]
|
||||||
AccessDbAlreadyExists {
|
AccessDbAlreadyExists {
|
||||||
value: String,
|
value: String,
|
||||||
|
ns: String,
|
||||||
|
db: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested root access method does not exist
|
/// The requested root access method does not exist
|
||||||
|
@ -966,15 +957,18 @@ pub enum Error {
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested namespace access method does not exist
|
/// The requested namespace access method does not exist
|
||||||
#[error("The namespace access method '{value}' does not exist")]
|
#[error("The access method '{value}' does not exist in the namespace '{ns}'")]
|
||||||
AccessNsNotFound {
|
AccessNsNotFound {
|
||||||
value: String,
|
value: String,
|
||||||
|
ns: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The requested database access method does not exist
|
/// The requested database access method does not exist
|
||||||
#[error("The database access method '{value}' does not exist")]
|
#[error("The access method '{value}' does not exist in the database '{db}'")]
|
||||||
AccessDbNotFound {
|
AccessDbNotFound {
|
||||||
value: String,
|
value: String,
|
||||||
|
ns: String,
|
||||||
|
db: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The access method cannot be defined on the requested level
|
/// The access method cannot be defined on the requested level
|
||||||
|
|
|
@ -474,6 +474,57 @@ pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Resul
|
||||||
)));
|
)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
// Check if this is root access
|
||||||
|
Claims {
|
||||||
|
ac: Some(ac),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Log the decoded authentication claims
|
||||||
|
trace!("Authenticating to root with access method `{}`", ac);
|
||||||
|
// Create a new readonly transaction
|
||||||
|
let mut tx = kvs.transaction(Read, Optimistic).await?;
|
||||||
|
// Get the namespace access method
|
||||||
|
let de = tx.get_root_access(&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
|
||||||
|
let roles = match token_data.claims.roles {
|
||||||
|
// If no role is provided, grant the viewer role
|
||||||
|
None => vec![Role::Viewer],
|
||||||
|
// If roles are provided, parse them
|
||||||
|
Some(roles) => roles
|
||||||
|
.iter()
|
||||||
|
.map(|r| -> Result<Role, Error> {
|
||||||
|
Role::from_str(r.as_str()).map_err(Error::IamError)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
};
|
||||||
|
// Log the success
|
||||||
|
trace!("Authenticated to root with access method `{}`", ac);
|
||||||
|
// Set the session
|
||||||
|
session.tk = Some(value);
|
||||||
|
session.ac = Some(ac.to_owned());
|
||||||
|
session.exp = expiration(de.duration.session)?;
|
||||||
|
session.au = Arc::new(Auth::new(Actor::new(de.name.to_string(), roles, Level::Root)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
// Check if this is root authentication with user credentials
|
// Check if this is root authentication with user credentials
|
||||||
Claims {
|
Claims {
|
||||||
id: Some(id),
|
id: Some(id),
|
||||||
|
@ -838,6 +889,110 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_token_root() {
|
||||||
|
let secret = "jwt_secret";
|
||||||
|
let key = EncodingKey::from_secret(secret.as_ref());
|
||||||
|
let claims = Claims {
|
||||||
|
iss: Some("surrealdb-test".to_string()),
|
||||||
|
iat: Some(Utc::now().timestamp()),
|
||||||
|
nbf: Some(Utc::now().timestamp()),
|
||||||
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
|
ac: Some("token".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 ACCESS token ON ROOT TYPE JWT ALGORITHM HS512 KEY '{secret}' DURATION FOR SESSION 30d").as_str(),
|
||||||
|
&sess,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Test without roles defined
|
||||||
|
//
|
||||||
|
{
|
||||||
|
// Prepare the claims object
|
||||||
|
let mut claims = claims.clone();
|
||||||
|
claims.roles = None;
|
||||||
|
// Create the token
|
||||||
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
|
// Signin with the token
|
||||||
|
let mut sess = Session::default();
|
||||||
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
|
assert_eq!(sess.ns, None);
|
||||||
|
assert_eq!(sess.db, None);
|
||||||
|
assert_eq!(sess.au.id(), "token");
|
||||||
|
assert!(sess.au.is_root());
|
||||||
|
assert!(sess.au.has_role(&Role::Viewer), "Auth user expected to 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 has been set explicitly
|
||||||
|
let exp = sess.exp.unwrap();
|
||||||
|
// Expiration should match the current time plus session duration with some margin
|
||||||
|
let min_exp = (Utc::now() + Duration::days(30) - Duration::seconds(10)).timestamp();
|
||||||
|
let max_exp = (Utc::now() + Duration::days(30) + Duration::seconds(10)).timestamp();
|
||||||
|
assert!(
|
||||||
|
exp > min_exp && exp < max_exp,
|
||||||
|
"Session expiration is expected to match the defined duration"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Test with roles defined
|
||||||
|
//
|
||||||
|
{
|
||||||
|
// Prepare the claims object
|
||||||
|
let mut claims = claims.clone();
|
||||||
|
claims.roles = Some(vec!["editor".to_string(), "owner".to_string()]);
|
||||||
|
// Create the token
|
||||||
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
|
// Signin with the token
|
||||||
|
let mut sess = Session::default();
|
||||||
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
|
assert_eq!(sess.ns, None);
|
||||||
|
assert_eq!(sess.db, None);
|
||||||
|
assert_eq!(sess.au.id(), "token");
|
||||||
|
assert!(sess.au.is_root());
|
||||||
|
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 have Editor role");
|
||||||
|
assert!(sess.au.has_role(&Role::Owner), "Auth user expected to have Owner role");
|
||||||
|
// Session expiration has been set explicitly
|
||||||
|
let exp = sess.exp.unwrap();
|
||||||
|
// Expiration should match the current time plus session duration with some margin
|
||||||
|
let min_exp = (Utc::now() + Duration::days(30) - Duration::seconds(10)).timestamp();
|
||||||
|
let max_exp = (Utc::now() + Duration::days(30) + Duration::seconds(10)).timestamp();
|
||||||
|
assert!(
|
||||||
|
exp > min_exp && exp < max_exp,
|
||||||
|
"Session expiration is expected to match the defined duration"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Test with invalid signature
|
||||||
|
//
|
||||||
|
{
|
||||||
|
// Prepare the claims object
|
||||||
|
let claims = claims.clone();
|
||||||
|
// Create the token
|
||||||
|
let key = EncodingKey::from_secret("invalid".as_ref());
|
||||||
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
|
// Signin with the token
|
||||||
|
let mut sess = Session::default();
|
||||||
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
|
assert!(res.is_err(), "Unexpected success signing in with token: {:?}", res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_token_ns() {
|
async fn test_token_ns() {
|
||||||
let secret = "jwt_secret";
|
let secret = "jwt_secret";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! How the keys are structured in the key value store
|
//! How the keys are structured in the key value store
|
||||||
///
|
///
|
||||||
/// crate::key::root::all /
|
/// crate::key::root::all /
|
||||||
|
/// crate::key::root::ac /!ac{ac}
|
||||||
/// crate::key::root::hb /!hb{ts}/{nd}
|
/// crate::key::root::hb /!hb{ts}/{nd}
|
||||||
/// crate::key::root::nd /!nd{nd}
|
/// crate::key::root::nd /!nd{nd}
|
||||||
/// crate::key::root::ni /!ni
|
/// crate::key::root::ni /!ni
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub enum Entry {
|
||||||
Pa(Arc<DefineParamStatement>),
|
Pa(Arc<DefineParamStatement>),
|
||||||
Tb(Arc<DefineTableStatement>),
|
Tb(Arc<DefineTableStatement>),
|
||||||
// Multi definitions
|
// Multi definitions
|
||||||
|
Acs(Arc<[DefineAccessStatement]>),
|
||||||
Azs(Arc<[DefineAnalyzerStatement]>),
|
Azs(Arc<[DefineAnalyzerStatement]>),
|
||||||
Dbs(Arc<[DefineDatabaseStatement]>),
|
Dbs(Arc<[DefineDatabaseStatement]>),
|
||||||
Das(Arc<[DefineAccessStatement]>),
|
Das(Arc<[DefineAccessStatement]>),
|
||||||
|
|
|
@ -1328,6 +1328,34 @@ impl Transaction {
|
||||||
Ok(val)
|
Ok(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve all ROOT access method definitions.
|
||||||
|
pub async fn all_root_accesses(&mut self) -> Result<Arc<[DefineAccessStatement]>, Error> {
|
||||||
|
let key = crate::key::root::ac::prefix();
|
||||||
|
Ok(if let Some(e) = self.cache.get(&key) {
|
||||||
|
if let Entry::Acs(v) = e {
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let beg = crate::key::root::ac::prefix();
|
||||||
|
let end = crate::key::root::ac::suffix();
|
||||||
|
let val = self.getr(beg..end, u32::MAX).await?;
|
||||||
|
let val = val.convert().into();
|
||||||
|
self.cache.set(key, Entry::Acs(Arc::clone(&val)));
|
||||||
|
val
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve all ROOT access method definitions in redacted form.
|
||||||
|
pub async fn all_root_accesses_redacted(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Arc<[DefineAccessStatement]>, Error> {
|
||||||
|
let accesses = self.all_root_accesses().await?;
|
||||||
|
let redacted: Vec<_> = accesses.iter().map(|statement| statement.redacted()).collect();
|
||||||
|
Ok(Arc::from(redacted))
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve all namespace definitions in a datastore.
|
/// Retrieve all namespace definitions in a datastore.
|
||||||
pub async fn all_ns(&mut self) -> Result<Arc<[DefineNamespaceStatement]>, Error> {
|
pub async fn all_ns(&mut self) -> Result<Arc<[DefineNamespaceStatement]>, Error> {
|
||||||
let key = crate::key::root::ns::prefix();
|
let key = crate::key::root::ns::prefix();
|
||||||
|
@ -1741,6 +1769,15 @@ impl Transaction {
|
||||||
Ok(val.into())
|
Ok(val.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve a specific root access method definition.
|
||||||
|
pub async fn get_root_access(&mut self, ac: &str) -> Result<DefineAccessStatement, Error> {
|
||||||
|
let key = crate::key::root::ac::new(ac);
|
||||||
|
let val = self.get(key).await?.ok_or(Error::AccessRootNotFound {
|
||||||
|
value: ac.to_owned(),
|
||||||
|
})?;
|
||||||
|
Ok(val.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve a specific namespace definition.
|
/// Retrieve a specific namespace definition.
|
||||||
pub async fn get_ns(&mut self, ns: &str) -> Result<DefineNamespaceStatement, Error> {
|
pub async fn get_ns(&mut self, ns: &str) -> Result<DefineNamespaceStatement, Error> {
|
||||||
let key = crate::key::root::ns::new(ns);
|
let key = crate::key::root::ns::new(ns);
|
||||||
|
@ -1771,8 +1808,9 @@ impl Transaction {
|
||||||
ac: &str,
|
ac: &str,
|
||||||
) -> Result<DefineAccessStatement, Error> {
|
) -> Result<DefineAccessStatement, Error> {
|
||||||
let key = crate::key::namespace::ac::new(ns, ac);
|
let key = crate::key::namespace::ac::new(ns, ac);
|
||||||
let val = self.get(key).await?.ok_or(Error::NaNotFound {
|
let val = self.get(key).await?.ok_or(Error::AccessNsNotFound {
|
||||||
value: ac.to_owned(),
|
value: ac.to_owned(),
|
||||||
|
ns: ns.to_owned(),
|
||||||
})?;
|
})?;
|
||||||
Ok(val.into())
|
Ok(val.into())
|
||||||
}
|
}
|
||||||
|
@ -1825,8 +1863,10 @@ impl Transaction {
|
||||||
ac: &str,
|
ac: &str,
|
||||||
) -> Result<DefineAccessStatement, Error> {
|
) -> Result<DefineAccessStatement, Error> {
|
||||||
let key = crate::key::database::ac::new(ns, db, ac);
|
let key = crate::key::database::ac::new(ns, db, ac);
|
||||||
let val = self.get(key).await?.ok_or(Error::DaNotFound {
|
let val = self.get(key).await?.ok_or(Error::AccessDbNotFound {
|
||||||
value: ac.to_owned(),
|
value: ac.to_owned(),
|
||||||
|
ns: ns.to_owned(),
|
||||||
|
db: db.to_owned(),
|
||||||
})?;
|
})?;
|
||||||
Ok(val.into())
|
Ok(val.into())
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,34 @@ impl DefineAccessStatement {
|
||||||
opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?;
|
opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?;
|
||||||
|
|
||||||
match &self.base {
|
match &self.base {
|
||||||
|
Base::Root => {
|
||||||
|
// Claim transaction
|
||||||
|
let mut run = ctx.tx_lock().await;
|
||||||
|
// Clear the cache
|
||||||
|
run.clear_cache();
|
||||||
|
// Check if access method already exists
|
||||||
|
if run.get_root_access(&self.name).await.is_ok() {
|
||||||
|
if self.if_not_exists {
|
||||||
|
return Ok(Value::None);
|
||||||
|
} else {
|
||||||
|
return Err(Error::AccessRootAlreadyExists {
|
||||||
|
value: self.name.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Process the statement
|
||||||
|
let key = crate::key::root::ac::new(&self.name);
|
||||||
|
run.set(
|
||||||
|
key,
|
||||||
|
DefineAccessStatement {
|
||||||
|
if_not_exists: false,
|
||||||
|
..self.clone()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
// Ok all good
|
||||||
|
Ok(Value::None)
|
||||||
|
}
|
||||||
Base::Ns => {
|
Base::Ns => {
|
||||||
// Claim transaction
|
// Claim transaction
|
||||||
let mut run = ctx.tx_lock().await;
|
let mut run = ctx.tx_lock().await;
|
||||||
|
@ -72,6 +100,7 @@ impl DefineAccessStatement {
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::AccessNsAlreadyExists {
|
return Err(Error::AccessNsAlreadyExists {
|
||||||
value: self.name.to_string(),
|
value: self.name.to_string(),
|
||||||
|
ns: opt.ns()?.into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,6 +130,8 @@ impl DefineAccessStatement {
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::AccessDbAlreadyExists {
|
return Err(Error::AccessDbAlreadyExists {
|
||||||
value: self.name.to_string(),
|
value: self.name.to_string(),
|
||||||
|
ns: opt.ns()?.into(),
|
||||||
|
db: opt.db()?.into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,12 @@ impl InfoStatement {
|
||||||
tmp.insert(v.name.to_string(), v.to_string().into());
|
tmp.insert(v.name.to_string(), v.to_string().into());
|
||||||
}
|
}
|
||||||
res.insert("users".to_owned(), tmp.into());
|
res.insert("users".to_owned(), tmp.into());
|
||||||
|
// Process the accesses
|
||||||
|
let mut tmp = Object::default();
|
||||||
|
for v in run.all_root_accesses_redacted().await?.iter() {
|
||||||
|
tmp.insert(v.name.to_string(), v.to_string().into());
|
||||||
|
}
|
||||||
|
res.insert("accesses".to_owned(), tmp.into());
|
||||||
// Ok all good
|
// Ok all good
|
||||||
Value::from(res).ok()
|
Value::from(res).ok()
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,19 @@ impl RemoveAccessStatement {
|
||||||
opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?;
|
opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?;
|
||||||
|
|
||||||
match &self.base {
|
match &self.base {
|
||||||
|
Base::Root => {
|
||||||
|
// Claim transaction
|
||||||
|
let mut run = ctx.tx_lock().await;
|
||||||
|
// Clear the cache
|
||||||
|
run.clear_cache();
|
||||||
|
// Get the definition
|
||||||
|
let ac = run.get_root_access(&self.name).await?;
|
||||||
|
// Delete the definition
|
||||||
|
let key = crate::key::root::ac::new(&ac.name);
|
||||||
|
run.del(key).await?;
|
||||||
|
// Ok all good
|
||||||
|
Ok(Value::None)
|
||||||
|
}
|
||||||
Base::Ns => {
|
Base::Ns => {
|
||||||
// Claim transaction
|
// Claim transaction
|
||||||
let mut run = ctx.tx_lock().await;
|
let mut run = ctx.tx_lock().await;
|
||||||
|
@ -59,10 +72,13 @@ impl RemoveAccessStatement {
|
||||||
.await;
|
.await;
|
||||||
match future {
|
match future {
|
||||||
Err(e) if self.if_exists => match e {
|
Err(e) if self.if_exists => match e {
|
||||||
Error::NaNotFound {
|
Error::AccessRootNotFound {
|
||||||
..
|
..
|
||||||
} => Ok(Value::None),
|
} => Ok(Value::None),
|
||||||
Error::DaNotFound {
|
Error::AccessNsNotFound {
|
||||||
|
..
|
||||||
|
} => Ok(Value::None),
|
||||||
|
Error::AccessDbNotFound {
|
||||||
..
|
..
|
||||||
} => Ok(Value::None),
|
} => Ok(Value::None),
|
||||||
e => Err(e),
|
e => Err(e),
|
||||||
|
|
|
@ -287,6 +287,14 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
t!("RECORD") => {
|
t!("RECORD") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
|
// The record access type can only be defined at the database level
|
||||||
|
if !matches!(res.base, Base::Db) {
|
||||||
|
unexpected!(
|
||||||
|
self,
|
||||||
|
t!("RECORD"),
|
||||||
|
"a valid access type at this level"
|
||||||
|
);
|
||||||
|
}
|
||||||
let mut ac = access_type::RecordAccess {
|
let mut ac = access_type::RecordAccess {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
|
@ -756,6 +756,66 @@ fn parse_define_access_jwt_key() {
|
||||||
res
|
res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// With comment. Asymmetric verify only. On namespace level.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON NAMESPACE TYPE JWT ALGORITHM EDDSA KEY "foo" COMMENT "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Ns,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::EdDSA,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: None,
|
||||||
|
}),
|
||||||
|
// Default durations.
|
||||||
|
duration: AccessDuration {
|
||||||
|
grant: None,
|
||||||
|
token: Some(Duration::from_hours(1)),
|
||||||
|
session: None,
|
||||||
|
},
|
||||||
|
comment: Some(Strand("bar".to_string())),
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// With comment. Asymmetric verify only. On root level.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON ROOT TYPE JWT ALGORITHM EDDSA KEY "foo" COMMENT "bar""#
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Statement::Define(DefineStatement::Access(DefineAccessStatement {
|
||||||
|
name: Ident("a".to_string()),
|
||||||
|
base: Base::Root,
|
||||||
|
kind: AccessType::Jwt(JwtAccess {
|
||||||
|
verify: JwtAccessVerify::Key(JwtAccessVerifyKey {
|
||||||
|
alg: Algorithm::EdDSA,
|
||||||
|
key: "foo".to_string(),
|
||||||
|
}),
|
||||||
|
issue: None,
|
||||||
|
}),
|
||||||
|
// Default durations.
|
||||||
|
duration: AccessDuration {
|
||||||
|
grant: None,
|
||||||
|
token: Some(Duration::from_hours(1)),
|
||||||
|
session: None,
|
||||||
|
},
|
||||||
|
comment: Some(Strand("bar".to_string())),
|
||||||
|
if_not_exists: false,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1132,6 +1192,28 @@ fn parse_define_access_record() {
|
||||||
res
|
res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Attempt to define record access at the root level.
|
||||||
|
{
|
||||||
|
let res = test_parse!(
|
||||||
|
parse_stmt,
|
||||||
|
r#"DEFINE ACCESS a ON ROOT TYPE RECORD DURATION FOR TOKEN NONE"#
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
res.is_err(),
|
||||||
|
"Unexpected successful parsing of record access at root level: {:?}",
|
||||||
|
res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Attempt to define record access at the namespace level.
|
||||||
|
{
|
||||||
|
let res =
|
||||||
|
test_parse!(parse_stmt, r#"DEFINE ACCESS a ON NS TYPE RECORD DURATION FOR TOKEN NONE"#);
|
||||||
|
assert!(
|
||||||
|
res.is_err(),
|
||||||
|
"Unexpected successful parsing of record access at namespace level: {:?}",
|
||||||
|
res
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -29,6 +29,7 @@ async fn define_statement_namespace() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
namespaces: { test: 'DEFINE NAMESPACE test' },
|
namespaces: { test: 'DEFINE NAMESPACE test' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
@ -1273,8 +1274,8 @@ async fn permissions_checks_define_ns() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ namespaces: { NS: 'DEFINE NAMESPACE NS' }, users: { } }"],
|
vec!["{ accesses: { }, namespaces: { NS: 'DEFINE NAMESPACE NS' }, users: { } }"],
|
||||||
vec!["{ namespaces: { }, users: { } }"],
|
vec!["{ accesses: { }, namespaces: { }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -1428,6 +1429,48 @@ async fn permissions_checks_define_analyzer() {
|
||||||
assert!(res.is_ok(), "{}", res.unwrap_err());
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn permissions_checks_define_access_root() {
|
||||||
|
let scenario = HashMap::from([
|
||||||
|
("prepare", ""),
|
||||||
|
("test", "DEFINE ACCESS access ON ROOT TYPE JWT ALGORITHM HS512 KEY 'secret'"),
|
||||||
|
("check", "INFO FOR ROOT"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
|
let check_results = [
|
||||||
|
vec!["{ accesses: { access: \"DEFINE ACCESS access ON ROOT TYPE JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION FOR TOKEN 1h, FOR SESSION NONE\" }, namespaces: { }, users: { } }"],
|
||||||
|
vec!["{ accesses: { }, namespaces: { }, users: { } }"]
|
||||||
|
];
|
||||||
|
|
||||||
|
let test_cases = [
|
||||||
|
// Root level
|
||||||
|
((().into(), Role::Owner), ("NS", "DB"), true),
|
||||||
|
((().into(), Role::Editor), ("NS", "DB"), false),
|
||||||
|
((().into(), Role::Viewer), ("NS", "DB"), false),
|
||||||
|
// Namespace level
|
||||||
|
((("NS",).into(), Role::Owner), ("NS", "DB"), false),
|
||||||
|
((("NS",).into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
||||||
|
((("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),
|
||||||
|
// Database level
|
||||||
|
((("NS", "DB").into(), Role::Owner), ("NS", "DB"), false),
|
||||||
|
((("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"), 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),
|
||||||
|
((("NS", "DB").into(), Role::Viewer), ("NS", "OTHER_DB"), false),
|
||||||
|
((("NS", "DB").into(), Role::Viewer), ("OTHER_NS", "DB"), false),
|
||||||
|
];
|
||||||
|
|
||||||
|
let res = iam_check_cases(test_cases.iter(), &scenario, check_results).await;
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn permissions_checks_define_access_ns() {
|
async fn permissions_checks_define_access_ns() {
|
||||||
let scenario = HashMap::from([
|
let scenario = HashMap::from([
|
||||||
|
@ -1522,8 +1565,8 @@ async fn permissions_checks_define_user_root() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ namespaces: { }, users: { user: \"DEFINE USER user ON ROOT PASSHASH 'secret' ROLES VIEWER DURATION FOR TOKEN 15m, FOR SESSION 6h\" } }"],
|
vec!["{ accesses: { }, namespaces: { }, users: { user: \"DEFINE USER user ON ROOT PASSHASH 'secret' ROLES VIEWER DURATION FOR TOKEN 15m, FOR SESSION 6h\" } }"],
|
||||||
vec!["{ namespaces: { }, users: { } }"]
|
vec!["{ accesses: { }, namespaces: { }, users: { } }"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -2105,9 +2148,9 @@ async fn define_remove_access() -> Result<(), Error> {
|
||||||
let mut t = Test::new(sql).await?;
|
let mut t = Test::new(sql).await?;
|
||||||
t.skip_ok(1)?;
|
t.skip_ok(1)?;
|
||||||
t.expect_val("None")?;
|
t.expect_val("None")?;
|
||||||
t.expect_error("The database access method 'example' already exists")?;
|
t.expect_error("The access method 'example' already exists in the database 'test'")?;
|
||||||
t.skip_ok(1)?;
|
t.skip_ok(1)?;
|
||||||
t.expect_error("The database access method 'example' does not exist")?;
|
t.expect_error("The access method 'example' does not exist in the database 'test'")?;
|
||||||
t.expect_val("None")?;
|
t.expect_val("None")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,19 +12,22 @@ async fn info_for_root() {
|
||||||
let sql = r#"
|
let sql = r#"
|
||||||
DEFINE NAMESPACE NS;
|
DEFINE NAMESPACE NS;
|
||||||
DEFINE USER user ON ROOT PASSWORD 'pass';
|
DEFINE USER user ON ROOT PASSWORD 'pass';
|
||||||
|
DEFINE ACCESS access ON ROOT TYPE JWT ALGORITHM HS512 KEY 'secret';
|
||||||
INFO FOR ROOT
|
INFO FOR ROOT
|
||||||
"#;
|
"#;
|
||||||
let dbs = new_ds().await.unwrap();
|
let dbs = new_ds().await.unwrap();
|
||||||
let ses = Session::owner();
|
let ses = Session::owner();
|
||||||
|
|
||||||
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
|
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
|
||||||
assert_eq!(res.len(), 3);
|
assert_eq!(res.len(), 4);
|
||||||
|
|
||||||
let out = res.pop().unwrap().output();
|
let out = res.pop().unwrap().output();
|
||||||
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
||||||
|
|
||||||
let output_regex =
|
let output_regex = Regex::new(
|
||||||
Regex::new(r"\{ namespaces: \{ NS: .* \}, users: \{ user: .* \} \}").unwrap();
|
r"\{ accesses: \{ access: .* \}, namespaces: \{ NS: .* \}, users: \{ user: .* \} \}",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let out_str = out.unwrap().to_string();
|
let out_str = out.unwrap().to_string();
|
||||||
assert!(
|
assert!(
|
||||||
output_regex.is_match(&out_str),
|
output_regex.is_match(&out_str),
|
||||||
|
@ -209,8 +212,10 @@ async fn permissions_checks_info_root() {
|
||||||
HashMap::from([("prepare", ""), ("test", "INFO FOR ROOT"), ("check", "INFO FOR ROOT")]);
|
HashMap::from([("prepare", ""), ("test", "INFO FOR ROOT"), ("check", "INFO FOR ROOT")]);
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results =
|
let check_results = [
|
||||||
[vec!["{ namespaces: { }, users: { } }"], vec!["{ namespaces: { }, users: { } }"]];
|
vec!["{ accesses: { }, namespaces: { }, users: { } }"],
|
||||||
|
vec!["{ accesses: { }, namespaces: { }, users: { } }"],
|
||||||
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
// Root level
|
// Root level
|
||||||
|
@ -537,11 +542,11 @@ async fn access_info_redacted() {
|
||||||
// Record
|
// Record
|
||||||
{
|
{
|
||||||
let sql = r#"
|
let sql = r#"
|
||||||
DEFINE ACCESS access ON NS TYPE RECORD WITH JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret';
|
DEFINE ACCESS access ON DB TYPE RECORD WITH JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret';
|
||||||
INFO FOR NS
|
INFO FOR DB
|
||||||
"#;
|
"#;
|
||||||
let dbs = new_ds().await.unwrap();
|
let dbs = new_ds().await.unwrap();
|
||||||
let ses = Session::owner().with_ns("ns");
|
let ses = Session::owner().with_ns("ns").with_db("test");
|
||||||
|
|
||||||
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
|
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
|
||||||
assert_eq!(res.len(), 2);
|
assert_eq!(res.len(), 2);
|
||||||
|
@ -550,7 +555,7 @@ async fn access_info_redacted() {
|
||||||
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
||||||
|
|
||||||
let out_expected =
|
let out_expected =
|
||||||
r#"{ accesses: { access: "DEFINE ACCESS access ON NAMESPACE TYPE RECORD WITH JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION FOR TOKEN 1h, FOR SESSION NONE" }, databases: { }, users: { } }"#.to_string();
|
r#"{ accesses: { access: "DEFINE ACCESS access ON DATABASE TYPE RECORD WITH JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION FOR TOKEN 1h, FOR SESSION NONE" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"#.to_string();
|
||||||
let out_str = out.unwrap().to_string();
|
let out_str = out.unwrap().to_string();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_str, out_expected,
|
out_str, out_expected,
|
||||||
|
@ -610,11 +615,11 @@ async fn access_info_redacted_structure() {
|
||||||
// Record
|
// Record
|
||||||
{
|
{
|
||||||
let sql = r#"
|
let sql = r#"
|
||||||
DEFINE ACCESS access ON NS TYPE RECORD WITH JWT ALGORITHM HS512 KEY 'secret' DURATION FOR TOKEN 15m, FOR SESSION 6h;
|
DEFINE ACCESS access ON DB TYPE RECORD WITH JWT ALGORITHM HS512 KEY 'secret' DURATION FOR TOKEN 15m, FOR SESSION 6h;
|
||||||
INFO FOR NS STRUCTURE
|
INFO FOR DB STRUCTURE
|
||||||
"#;
|
"#;
|
||||||
let dbs = new_ds().await.unwrap();
|
let dbs = new_ds().await.unwrap();
|
||||||
let ses = Session::owner().with_ns("ns");
|
let ses = Session::owner().with_ns("ns").with_db("db");
|
||||||
|
|
||||||
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
|
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
|
||||||
assert_eq!(res.len(), 2);
|
assert_eq!(res.len(), 2);
|
||||||
|
@ -623,7 +628,7 @@ async fn access_info_redacted_structure() {
|
||||||
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
assert!(out.is_ok(), "Unexpected error: {:?}", out);
|
||||||
|
|
||||||
let out_expected =
|
let out_expected =
|
||||||
r#"{ accesses: [{ base: 'NAMESPACE', duration: { session: 6h, token: 15m }, kind: { jwt: { issuer: { alg: 'HS512', key: '[REDACTED]' }, verify: { alg: 'HS512', key: '[REDACTED]' } }, kind: 'RECORD' }, name: 'access' }], databases: [], users: [] }"#.to_string();
|
r#"{ accesses: [{ base: 'DATABASE', duration: { session: 6h, token: 15m }, kind: { jwt: { issuer: { alg: 'HS512', key: '[REDACTED]' }, verify: { alg: 'HS512', key: '[REDACTED]' } }, kind: 'RECORD' }, name: 'access' }], analyzers: [], functions: [], models: [], params: [], tables: [], users: [] }"#.to_string();
|
||||||
let out_str = out.unwrap().to_string();
|
let out_str = out.unwrap().to_string();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_str, out_expected,
|
out_str, out_expected,
|
||||||
|
|
|
@ -522,7 +522,7 @@ async fn should_error_when_remove_and_access_does_not_exist() -> Result<(), Erro
|
||||||
assert_eq!(res.len(), 1);
|
assert_eq!(res.len(), 1);
|
||||||
//
|
//
|
||||||
let tmp = res.remove(0).result.unwrap_err();
|
let tmp = res.remove(0).result.unwrap_err();
|
||||||
assert!(matches!(tmp, Error::DaNotFound { .. }),);
|
assert!(matches!(tmp, Error::AccessDbNotFound { .. }),);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -589,8 +589,8 @@ async fn permissions_checks_remove_ns() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ namespaces: { }, users: { } }"],
|
vec!["{ accesses: { }, namespaces: { }, users: { } }"],
|
||||||
vec!["{ namespaces: { NS: 'DEFINE NAMESPACE NS' }, users: { } }"],
|
vec!["{ accesses: { }, namespaces: { NS: 'DEFINE NAMESPACE NS' }, users: { } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
@ -747,6 +747,48 @@ async fn permissions_checks_remove_analyzer() {
|
||||||
assert!(res.is_ok(), "{}", res.unwrap_err());
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn permissions_checks_remove_root_access() {
|
||||||
|
let scenario = HashMap::from([
|
||||||
|
("prepare", "DEFINE ACCESS access ON ROOT TYPE JWT ALGORITHM HS512 KEY 'secret'"),
|
||||||
|
("test", "REMOVE ACCESS access ON ROOT"),
|
||||||
|
("check", "INFO FOR ROOT"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
|
let check_results = [
|
||||||
|
vec!["{ accesses: { }, namespaces: { }, users: { } }"],
|
||||||
|
vec!["{ accesses: { access: \"DEFINE ACCESS access ON ROOT TYPE JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION FOR TOKEN 1h, FOR SESSION NONE\" }, namespaces: { }, users: { } }"],
|
||||||
|
];
|
||||||
|
|
||||||
|
let test_cases = [
|
||||||
|
// Root level
|
||||||
|
((().into(), Role::Owner), ("NS", "DB"), true),
|
||||||
|
((().into(), Role::Editor), ("NS", "DB"), false),
|
||||||
|
((().into(), Role::Viewer), ("NS", "DB"), false),
|
||||||
|
// Namespace level
|
||||||
|
((("NS",).into(), Role::Owner), ("NS", "DB"), false),
|
||||||
|
((("NS",).into(), Role::Owner), ("OTHER_NS", "DB"), false),
|
||||||
|
((("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),
|
||||||
|
// Database level
|
||||||
|
((("NS", "DB").into(), Role::Owner), ("NS", "DB"), false),
|
||||||
|
((("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"), 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),
|
||||||
|
((("NS", "DB").into(), Role::Viewer), ("NS", "OTHER_DB"), false),
|
||||||
|
((("NS", "DB").into(), Role::Viewer), ("OTHER_NS", "DB"), false),
|
||||||
|
];
|
||||||
|
|
||||||
|
let res = iam_check_cases(test_cases.iter(), &scenario, check_results).await;
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn permissions_checks_remove_ns_access() {
|
async fn permissions_checks_remove_ns_access() {
|
||||||
let scenario = HashMap::from([
|
let scenario = HashMap::from([
|
||||||
|
@ -841,8 +883,8 @@ async fn permissions_checks_remove_root_user() {
|
||||||
|
|
||||||
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
// Define the expected results for the check statement when the test statement succeeded and when it failed
|
||||||
let check_results = [
|
let check_results = [
|
||||||
vec!["{ namespaces: { }, users: { } }"],
|
vec!["{ accesses: { }, namespaces: { }, users: { } }"],
|
||||||
vec!["{ namespaces: { }, users: { user: \"DEFINE USER user ON ROOT PASSHASH 'secret' ROLES VIEWER DURATION FOR TOKEN 1h, FOR SESSION NONE\" } }"],
|
vec!["{ accesses: { }, namespaces: { }, users: { user: \"DEFINE USER user ON ROOT PASSHASH 'secret' ROLES VIEWER DURATION FOR TOKEN 1h, FOR SESSION NONE\" } }"],
|
||||||
];
|
];
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
|
|
|
@ -233,6 +233,7 @@ async fn loose_mode_all_ok() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
"{
|
"{
|
||||||
|
accesses: {},
|
||||||
namespaces: { test: 'DEFINE NAMESPACE test' },
|
namespaces: { test: 'DEFINE NAMESPACE test' },
|
||||||
users: {},
|
users: {},
|
||||||
}",
|
}",
|
||||||
|
|
Loading…
Reference in a new issue