335 lines
9.3 KiB
Rust
335 lines
9.3 KiB
Rust
#![allow(unused_imports, dead_code)]
|
|
|
|
pub mod common;
|
|
|
|
pub use common::{TestContext, bakery_chain::*, setup::*};
|
|
use sea_orm::{
|
|
ColumnTrait, ConnectionTrait, DbConn, DbErr, EntityName, EntityTrait, IntoActiveModel, NotSet,
|
|
QueryFilter, Set, TransactionTrait, entity::prelude::ChronoUtc,
|
|
};
|
|
|
|
#[sea_orm_macros::test]
|
|
#[cfg(feature = "rbac")]
|
|
async fn main() {
|
|
let ctx = TestContext::new("bakery_chain_rbac_tests").await;
|
|
create_tables(&ctx.db).await.unwrap();
|
|
sea_orm::rbac::schema::create_tables(&ctx.db, Default::default())
|
|
.await
|
|
.unwrap();
|
|
rbac_setup(&ctx.db).await.unwrap();
|
|
crud_tests(&ctx.db).await.unwrap();
|
|
ctx.delete().await;
|
|
}
|
|
|
|
#[cfg(feature = "rbac")]
|
|
async fn rbac_setup(db: &DbConn) -> Result<(), DbErr> {
|
|
use sea_orm::rbac::{RbacAddRoleHierarchy, RbacContext};
|
|
|
|
let mut context = RbacContext::load(db).await?;
|
|
|
|
let tables = [
|
|
baker::Entity.table_name(),
|
|
bakery::Entity.table_name(),
|
|
cake::Entity.table_name(),
|
|
cakes_bakers::Entity.table_name(),
|
|
customer::Entity.table_name(),
|
|
lineitem::Entity.table_name(),
|
|
order::Entity.table_name(),
|
|
"*", // WILDCARD
|
|
];
|
|
|
|
context.add_tables(db, &tables).await?;
|
|
|
|
context.add_crud_permissions(db).await?;
|
|
|
|
context
|
|
.add_roles(db, &["admin", "manager", "public"])
|
|
.await?;
|
|
|
|
context
|
|
.assign_user_role(db, &[(1, "admin"), (2, "manager"), (3, "public")])
|
|
.await?;
|
|
|
|
// public can select everything
|
|
context
|
|
.add_role_permissions(db, "public", &["select"], &["*"])
|
|
.await?;
|
|
|
|
// manager can create / update everything except bakery
|
|
context
|
|
.add_role_permissions(
|
|
db,
|
|
"manager",
|
|
&["insert", "update"],
|
|
&tables
|
|
.iter()
|
|
.cloned()
|
|
.filter(|t| !matches!(*t, "bakery" | "*"))
|
|
.collect::<Vec<_>>(),
|
|
)
|
|
.await?;
|
|
|
|
// manager can delete order
|
|
context
|
|
.add_role_permissions(db, "manager", &["delete"], &["order", "lineitem"])
|
|
.await?;
|
|
|
|
// admin can do anything, in addition to public / manager
|
|
context
|
|
.add_role_permissions(db, "admin", &["delete"], &["*"])
|
|
.await?;
|
|
|
|
// add permissions to bakery which manager doesn't have
|
|
context
|
|
.add_role_permissions(db, "admin", &["insert", "update"], &["bakery"])
|
|
.await?;
|
|
|
|
context
|
|
.add_role_hierarchy(
|
|
db,
|
|
&[
|
|
RbacAddRoleHierarchy {
|
|
super_role: "admin",
|
|
role: "manager",
|
|
},
|
|
RbacAddRoleHierarchy {
|
|
super_role: "manager",
|
|
role: "public",
|
|
},
|
|
],
|
|
)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "rbac")]
|
|
async fn crud_tests(db: &DbConn) -> Result<(), DbErr> {
|
|
use sea_orm::{RestrictedConnection, RestrictedTransaction, rbac::RbacUserId};
|
|
|
|
db.load_rbac().await?;
|
|
|
|
let admin = RbacUserId(1);
|
|
let manager = RbacUserId(2);
|
|
let public = RbacUserId(3);
|
|
|
|
async fn admin_create_bakery(db: RestrictedConnection) -> Result<(), DbErr> {
|
|
// only admin can create bakery
|
|
let seaside_bakery = bakery::ActiveModel {
|
|
name: Set("SeaSide Bakery".to_owned()),
|
|
profit_margin: Set(10.2),
|
|
..Default::default()
|
|
};
|
|
let res = Bakery::insert(seaside_bakery).exec(&db).await?;
|
|
let bakery: Option<bakery::Model> = Bakery::find_by_id(res.last_insert_id).one(&db).await?;
|
|
|
|
assert_eq!(bakery.unwrap().name, "SeaSide Bakery");
|
|
Ok(())
|
|
}
|
|
|
|
admin_create_bakery(db.restricted_for(admin)?).await?;
|
|
|
|
// manager / public can't create bakery
|
|
for user in [manager, public] {
|
|
assert!(matches!(
|
|
Bakery::insert(bakery::ActiveModel::default())
|
|
.exec(&db.restricted_for(user)?)
|
|
.await,
|
|
Err(DbErr::AccessDenied { .. })
|
|
));
|
|
let txn = db.restricted_for(user)?.begin().await?;
|
|
assert!(matches!(
|
|
Bakery::insert(bakery::ActiveModel::default())
|
|
.exec(&txn)
|
|
.await,
|
|
Err(DbErr::AccessDenied { .. })
|
|
));
|
|
}
|
|
|
|
// anyone can read bakery
|
|
for user_id in [1, 2, 3] {
|
|
let db = db.restricted_for(RbacUserId(user_id))?;
|
|
|
|
let bakery = Bakery::find().one(&db).await?.unwrap();
|
|
assert_eq!(bakery.name, "SeaSide Bakery");
|
|
}
|
|
|
|
// manager can create cake / baker
|
|
{
|
|
let db = db.restricted_for(manager)?;
|
|
|
|
cake::Entity::insert(cake::ActiveModel {
|
|
name: Set("Cheesecake".to_owned()),
|
|
price: Set(2.into()),
|
|
bakery_id: Set(Some(1)),
|
|
gluten_free: Set(false),
|
|
..Default::default()
|
|
})
|
|
.exec(&db)
|
|
.await
|
|
.expect("insert succeeds");
|
|
|
|
db.transaction_async::<_, _, DbErr>(async |txn| {
|
|
cake::Entity::insert(cake::ActiveModel {
|
|
name: Set("Chocolate".to_owned()),
|
|
price: Set(3.into()),
|
|
bakery_id: Set(Some(1)),
|
|
gluten_free: Set(true),
|
|
..Default::default()
|
|
})
|
|
.exec(txn)
|
|
.await?;
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.expect("insert succeeds");
|
|
|
|
let txn: RestrictedTransaction = db.begin().await?;
|
|
|
|
baker::Entity::insert(baker::ActiveModel {
|
|
name: Set("Master Baker".to_owned()),
|
|
contact_details: Set(Default::default()),
|
|
bakery_id: Set(Some(1)),
|
|
..Default::default()
|
|
})
|
|
.exec(&txn)
|
|
.await
|
|
.expect("insert succeeds");
|
|
|
|
txn.commit().await?;
|
|
}
|
|
|
|
assert_eq!(cake::Entity::find().all(db).await?.len(), 2);
|
|
|
|
// anyone can read cake
|
|
for user_id in [1, 2, 3] {
|
|
let db = db.restricted_for(RbacUserId(user_id))?;
|
|
|
|
let cake = cake::Entity::find().one(&db).await?.unwrap();
|
|
assert_eq!(cake.name, "Cheesecake");
|
|
}
|
|
|
|
// admin can create customer
|
|
{
|
|
let db = db.restricted_for(admin)?;
|
|
|
|
customer::Entity::insert(customer::ActiveModel {
|
|
id: Set(11),
|
|
name: Set("Alice".to_owned()),
|
|
notes: Set(None),
|
|
})
|
|
.exec(&db)
|
|
.await?;
|
|
|
|
customer::Entity::insert(customer::ActiveModel {
|
|
id: Set(12),
|
|
name: Set("Bob".to_owned()),
|
|
notes: Set(None),
|
|
})
|
|
.exec(&db)
|
|
.await?;
|
|
}
|
|
|
|
// manager can create / delete order
|
|
{
|
|
let public_db = db.restricted_for(public)?;
|
|
let db = db.restricted_for(manager)?;
|
|
|
|
order::Entity::insert(order::ActiveModel {
|
|
id: Set(101),
|
|
total: Set(10.into()),
|
|
bakery_id: Set(1),
|
|
customer_id: Set(11),
|
|
placed_at: Set(ChronoUtc::now()),
|
|
})
|
|
.exec(&db)
|
|
.await?;
|
|
|
|
lineitem::Entity::insert(lineitem::ActiveModel {
|
|
id: NotSet,
|
|
price: Set(2.into()),
|
|
quantity: Set(2),
|
|
order_id: Set(101),
|
|
cake_id: Set(1),
|
|
})
|
|
.exec(&db)
|
|
.await?;
|
|
|
|
let to_insert = lineitem::ActiveModel {
|
|
id: NotSet,
|
|
price: Set(3.into()),
|
|
quantity: Set(3),
|
|
order_id: Set(101),
|
|
cake_id: Set(2),
|
|
};
|
|
let lineitem_id = if db.support_returning() {
|
|
lineitem::Entity::insert(to_insert)
|
|
.exec_with_returning(&db)
|
|
.await?
|
|
.id
|
|
} else {
|
|
lineitem::Entity::insert(to_insert)
|
|
.exec(&db)
|
|
.await?
|
|
.last_insert_id
|
|
};
|
|
|
|
let order_with_items = order::Entity::find()
|
|
.find_with_related(lineitem::Entity)
|
|
.all(&public_db)
|
|
.await?;
|
|
assert_eq!(order_with_items[0].1.len(), 2);
|
|
|
|
lineitem::Entity::delete_many()
|
|
.filter(lineitem::Column::Id.eq(lineitem_id))
|
|
.exec(&db)
|
|
.await?;
|
|
|
|
// reject; of course
|
|
assert!(matches!(
|
|
lineitem::Entity::delete_many()
|
|
.filter(lineitem::Column::Id.eq(lineitem_id))
|
|
.exec(&public_db)
|
|
.await,
|
|
Err(DbErr::AccessDenied { .. })
|
|
));
|
|
|
|
// only 1 line item left
|
|
let order_with_items = order::Entity::find()
|
|
.find_with_related(lineitem::Entity)
|
|
.all(&public_db)
|
|
.await?;
|
|
assert_eq!(order_with_items[0].1.len(), 1);
|
|
}
|
|
|
|
// manager can update order
|
|
{
|
|
use sea_orm::ActiveModelTrait;
|
|
|
|
let db = db.restricted_for(manager)?;
|
|
|
|
let lineitem = lineitem::Entity::find_by_id(1).one(&db).await?.unwrap();
|
|
assert_eq!(lineitem.quantity, 2);
|
|
let mut lineitem = lineitem.into_active_model();
|
|
lineitem.quantity = Set(3);
|
|
let lineitem = lineitem.save(&db).await?;
|
|
assert_eq!(lineitem.quantity.unwrap(), 3);
|
|
}
|
|
|
|
// only admin can delete customer
|
|
{
|
|
use sea_orm::ModelTrait;
|
|
|
|
let db = db.restricted_for(admin)?;
|
|
|
|
let bob = customer::Entity::find_by_id(12).one(&db).await?.unwrap();
|
|
assert_eq!(bob.name, "Bob");
|
|
|
|
bob.delete(&db).await?;
|
|
assert!(customer::Entity::find_by_id(12).one(&db).await?.is_none());
|
|
}
|
|
|
|
Ok(())
|
|
}
|