1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
//! Actionable provides the basic functionality needed to build an async-based
//! API that has a flexible permissions system integrated.
//!
//! This crate was designed to be used by [`BonsaiDb`](https://bonsaidb.io/)
//! internally, and as a way for users of `BonsaiDb` to extend their database
//! servers with their own APIs.
//!
//! ## Permissions
//!
//! The [`Permissions`] struct is constructed from a list of `Statement`s. The
//! `Statement` struct is inspired by [statements in
//! IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_statement.html).
//! By default, all actions are denied for all resources.
//!
//! The [`ResourceName`] struct describes a unique name/id of *anything* in your
//! application. This is meant to be similar to [ARNs in
//! IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns),
//! but instead of being restricted to a format by this library, you are able to
//! define your own syntax.
//!
//! The [`Action`] trait is derive-able, and will convert any enum to something
//! that can be permitted or denied to any [`ResourceName`]. This derive macro
//! only supports enums with variants that have no parameters, or only have a
//! single name-less parameter that also implements [`Action`].
//!
//! An example [`Action`] enum might look like:
//!
//! ```rust
//! # use actionable::Action;
//! #[derive(Action, Debug)]
//! pub enum AllActions {
//! FlushCache,
//! User(UserActions),
//! }
//!
//! #[derive(Action, Debug)]
//! pub enum UserActions {
//! Create,
//! ChangeUsername,
//! Delete,
//! }
//! ```
//!
//! An example permissions check for `users.42` might look like:
//!
//! ```rust
//! # use actionable::{Action, Permissions, ResourceName};
//! # #[derive(Action, Debug)]
//! # pub enum AllActions {
//! # FlushCache,
//! # User(UserActions)
//! # }
//! #
//! # #[derive(Action, Debug)]
//! # pub enum UserActions {
//! # Create,
//! # ChangeUsername,
//! # Delete,
//! # }
//! # let permissions = Permissions::default();
//! let allowed = permissions.allowed_to(
//! &ResourceName::named("users").and(42),
//! &AllActions::User(UserActions::Delete),
//! );
//! ```
//!
//! ## Configuration
//!
//! Along with allowing actions to be taken, Actionable can be used to configure
//! values that may change on a per-role basis. Rate limits are an example of
//! what this API is designed to handle:
//!
//! ```rust
//! # use actionable::{Permissions, Statement, ResourceName, Configuration};
//! let permissions = Permissions::from(Statement::for_any().with("rate-limit", 500_u64));
//! let effective_rate_limit = permissions
//! .get(&ResourceName::named("core-api"), "rate-limit")
//! .and_then(Configuration::to_unsigned);
//! assert_eq!(effective_rate_limit, Some(500));
//! ```
//!
//! ## Permission-driven async API
//!
//! At the core of many networked APIs written in Rust is an enum that
//! represents a request, and similarly there are usually common response/error
//! types. In these applications, there is usually a manually-written match
//! statement that, for readability and maintainability, simply pass the
//! parameters from the request to a helper method to handle the actual logic of
//! the request.
//!
//! The goal of the API portion of this crate is to replace the aforementioned
//! boilerplate match statement with a simple derive macro. For a commented
//! example, check out
//! [`actionable/examples/api-simulator.rs`](https://github.com/khonsulabs/actionable/blob/main/actionable/examples/api-simulator.rs).
#![forbid(unsafe_code)]
#![warn(
clippy::cargo,
missing_docs,
clippy::pedantic,
future_incompatible,
rust_2018_idioms
)]
#![cfg_attr(doc, deny(rustdoc::all))]
mod action;
mod dispatcher;
mod permissions;
mod statement;
pub use actionable_macros::Actionable;
#[doc(hidden)]
pub use async_trait::async_trait;
use serde::{Deserialize, Serialize};
pub use self::{
action::{Action, ActionName},
dispatcher::Dispatcher,
permissions::Permissions,
statement::{ActionNameList, Configuration, Identifier, ResourceName, Statement},
};
#[cfg(test)]
mod tests;
/// An `action` was denied.
#[derive(thiserror::Error, Clone, Debug, Serialize, Deserialize)]
#[error("Action '{action}' was denied on resource '{resource}'")]
pub struct PermissionDenied {
/// The resource that `action` was attempted upon.
pub resource: ResourceName<'static>,
/// The `action` attempted upon `resource`.
pub action: ActionName,
}