Lines
94.22 %
Functions
83.75 %
Branches
100 %
#![allow(unused_variables)]
use std::borrow::Cow;
use crate::{
Action, ActionName, Actionable, AsyncDispatcher, PermissionDenied, Permissions, ResourceName,
Statement,
};
#[derive(Debug, Action)]
#[action(actionable = crate)]
enum TestActions {
DoSomething,
Post(PostActions),
}
enum PostActions {
Read,
Update,
Delete,
#[test]
fn basics() {
// Default action is deny
let statements = vec![
// Allow Read on all.
Statement::for_any().allowing(&TestActions::Post(PostActions::Read)),
// Allow all actions for the resource named all-actions-allowed
Statement::for_resource("all-actions-allowed").allowing_all(),
// Allow all Post actions for the resource named only-post-actions-allowed
Statement::for_resource("only-post-actions-allowed")
.allowing(&ActionName(vec![Cow::Borrowed("Post")])),
];
let permissions = Permissions::from(statements);
// Check the positive cases:
assert!(permissions.allowed_to(
&ResourceName::named("someresource"),
&TestActions::Post(PostActions::Read)
));
&ResourceName::named("all-actions-allowed"),
&TestActions::Post(PostActions::Update)
&TestActions::DoSomething
&ResourceName::named("only-post-actions-allowed"),
&TestActions::Post(PostActions::Delete)
// Test the negatives
assert!(!permissions.allowed_to(
fn multiple_actions() {
Statement::for_any()
.allowing(&TestActions::Post(PostActions::Read))
.allowing(&TestActions::Post(PostActions::Delete)),
// Check another permission
#[derive(Actionable, Debug)]
#[actionable(actionable = crate, async)]
enum Request {
#[actionable(protection = "none")]
UnprotectedNoParameters,
UnprotectedEnumParameter(u64),
UnprotectedStructParameter { value: u64 },
#[actionable(protection = "simple")]
SimplyProtectedNoParameters,
SimplyProtectedEnumParameter(u64),
SimplyProtectedStructParameter { value: u64 },
#[actionable(protection = "custom")]
CustomProtectedNoParameters,
CustomProtectedEnumParameter(u64),
CustomProtectedStructParameter { value: u64 },
#[derive(AsyncDispatcher, Debug)]
#[dispatcher(input = Request, actionable = crate)]
struct TestDispatcher;
#[async_trait::async_trait]
impl RequestDispatcher for TestDispatcher {
type Error = TestError;
type Output = Option<u64>;
#[derive(thiserror::Error, Debug)]
pub enum TestError {
#[error("custom error")]
CustomError,
#[error("permission error: {0}")]
PermissionDenied(#[from] PermissionDenied),
impl UnprotectedEnumParameterHandler for TestDispatcher {
async fn handle(
&self,
_permissions: &Permissions,
arg1: u64,
) -> Result<Option<u64>, TestError> {
Ok(Some(arg1))
impl UnprotectedStructParameterHandler for TestDispatcher {
value: u64,
Ok(Some(value))
impl UnprotectedNoParametersHandler for TestDispatcher {
async fn handle(&self, _permissions: &Permissions) -> Result<Option<u64>, TestError> {
Ok(None)
impl SimplyProtectedEnumParameterHandler for TestDispatcher {
type Action = TestActions;
async fn resource_name<'a>(&'a self, arg1: &'a u64) -> Result<ResourceName<'a>, TestError> {
Ok(ResourceName::named(*arg1))
fn action() -> Self::Action {
TestActions::DoSomething
async fn handle_protected(
impl SimplyProtectedStructParameterHandler for TestDispatcher {
impl SimplyProtectedNoParametersHandler for TestDispatcher {
async fn resource_name<'a>(&'a self) -> Result<ResourceName<'a>, TestError> {
Ok(ResourceName::named(0))
async fn handle_protected(&self, _permissions: &Permissions) -> Result<Option<u64>, TestError> {
impl CustomProtectedNoParametersHandler for TestDispatcher {
async fn verify_permissions(&self, permissions: &Permissions) -> Result<(), TestError> {
if permissions.allowed_to(&ResourceName::named(0), &TestActions::DoSomething) {
Ok(())
} else {
Err(TestError::CustomError)
impl CustomProtectedEnumParameterHandler for TestDispatcher {
async fn verify_permissions(
permissions: &Permissions,
arg1: &u64,
) -> Result<(), TestError> {
if permissions.allowed_to(&ResourceName::named(*arg1), &TestActions::DoSomething) {
impl CustomProtectedStructParameterHandler for TestDispatcher {
enum GenericRequest<T> {
NonGeneric,
#[actionable(protection = "none", subaction)]
Sub(T),
#[dispatcher(
input = GenericRequest<Request>,
actionable = crate
)]
struct GenericDispatcher;
impl GenericRequestDispatcher for GenericDispatcher {
type Subaction = Request;
async fn handle_subaction(
subaction: Request,
TestDispatcher.dispatch(permissions, subaction).await
impl NonGenericHandler for GenericDispatcher {
async fn handle(&self, permissions: &Permissions) -> Result<Option<u64>, TestError> {
Ok(Some(52))
#[tokio::test]
#[allow(clippy::too_many_lines)]
async fn example() {
let permissions = Permissions::from(vec![Statement::for_resource(42).allowing_all()]);
let dispatcher = TestDispatcher;
// All success (permitted) cases
assert_eq!(
dispatcher
.dispatch(&permissions, Request::UnprotectedEnumParameter(42),)
.await
.unwrap(),
Some(42)
);
.dispatch(&permissions, Request::UnprotectedStructParameter {
value: 42
},)
.dispatch(&permissions, Request::UnprotectedNoParameters,)
None
.dispatch(&permissions, Request::SimplyProtectedEnumParameter(42))
.dispatch(&permissions, Request::SimplyProtectedStructParameter {
.dispatch(&permissions, Request::CustomProtectedEnumParameter(42))
.dispatch(&permissions, Request::CustomProtectedStructParameter {
// Generic dispatching
assert!(matches!(
GenericDispatcher
.dispatch(&permissions, GenericRequest::NonGeneric,)
.await,
.dispatch(
&permissions,
GenericRequest::Sub(Request::CustomProtectedEnumParameter(42)),
)
Ok(Some(42))
// Permission denied errors
.dispatch(&permissions, Request::SimplyProtectedNoParameters)
Err(TestError::PermissionDenied(_))
.dispatch(&permissions, Request::SimplyProtectedEnumParameter(1))
value: 1
// Custom errors
.dispatch(&permissions, Request::CustomProtectedNoParameters)
.dispatch(&permissions, Request::CustomProtectedEnumParameter(1))
fn allowed_actions_merging_tests() {
let permissions_a = Permissions::from(vec![
Statement::for_resource(ResourceName::named("started_with_all")).allowing_all(),
Statement::for_resource(ResourceName::named("started_with_some"))
.allowing(&TestActions::DoSomething),
Statement::for_resource(ResourceName::named("nested").and("started_with_all"))
.allowing_all(),
Statement::for_resource(ResourceName::named("nested").and("started_with_some"))
]);
let permissions_b = Permissions::from(vec![
Statement::for_resource(ResourceName::named("started_with_none")).allowing_all(),
Statement::for_resource(ResourceName::named("started_with_some")).allowing_all(),
Statement::for_resource(ResourceName::named("nested").and("started_with_none"))
.allowing(&TestActions::Post(PostActions::Read)),
let merged = Permissions::merged([permissions_a, permissions_b].iter());
// For the top level, on Actions we're only testing transitioning form either
// None/Some to All
assert!(merged.allowed_to(
&ResourceName::named("started_with_all"),
&ResourceName::named("started_with_none"),
&ResourceName::named("started_with_some"),
// For the nested level, the transitions will only take permissions to a Some()
// instead of All.
&ResourceName::named("nested").and("started_with_none"),
assert!(!merged.allowed_to(
&ResourceName::named("nested").and("started_with_some"),
fn configuration_tests() {
Statement::for_resource(ResourceName::named("a")).with("u64", 0_u64),
Statement::for_resource(ResourceName::named("a")).with("i64", i64::MIN),
Statement::for_resource(ResourceName::named("a").and("b")).with("i64", 2_i64),
Statement::for_any().with("global", "value"),
permissions_a
.get(&ResourceName::named("a"), "u64")
.unwrap()
.to_unsigned(),
Some(0)
.to_signed(),
.to_string(),
"0"
.get(&ResourceName::named("a"), "i64")
Some(i64::MIN)
.get(&ResourceName::named("a").and("b"), "i64")
Some(2)
.get(&ResourceName::named("a").and("b"), "global")
"value"
Statement::for_resource(ResourceName::named("a")).with("newkey", "newvalue"),
Statement::for_any().with("global", "value2"),
let merged = Permissions::merged([&permissions_a, &permissions_b]);
merged
.get(&ResourceName::named("a").and("b"), "newkey")
"newvalue"