use std::{
io::{self, BufRead},
sync::Arc,
};
use actionable::{Action, Actionable, Dispatcher, Permissions, ResourceName, Statement};
use async_trait::async_trait;
use tokio::sync::Mutex;
#[derive(Actionable, Debug)]
pub enum ApiRequest {
#[actionable(protection = "none")]
ListUsers,
#[actionable(protection = "custom")]
AddUser(String),
#[actionable(protection = "simple")]
DeleteUser { username: String },
}
pub enum ApiResponse {
Empty,
UserAdded,
UserDeleted,
}
#[derive(Action, Debug)]
enum ApiActions {
AddUser,
DeleteUser,
}
#[derive(Dispatcher, Debug)]
#[dispatcher(input = ApiRequest)]
struct Server {
users: Arc<Mutex<Vec<String>>>,
}
impl ApiRequestDispatcher for Server {
type Error = anyhow::Error;
type Output = ApiResponse;
}
#[async_trait]
impl ListUsersHandler for Server {
async fn handle(&self, _permissions: &Permissions) -> anyhow::Result<ApiResponse> {
let users = self.users.lock().await;
println!("Current users:");
for user in users.iter() {
println!("{}", user)
}
Ok(ApiResponse::Empty)
}
}
#[async_trait]
impl AddUserHandler for Server {
async fn verify_permissions(
&self,
permissions: &Permissions,
username: &String,
) -> anyhow::Result<()> {
if permissions.allowed_to(ResourceName::named(username), &ApiActions::AddUser) {
Ok(())
} else {
anyhow::bail!("Not allowed to delete users")
}
}
async fn handle_protected(
&self,
_permissions: &Permissions,
username: String,
) -> anyhow::Result<ApiResponse> {
let mut users = self.users.lock().await;
users.push(username);
users.sort();
println!("User added.");
Ok(ApiResponse::UserAdded)
}
}
#[async_trait]
impl DeleteUserHandler for Server {
type Action = ApiActions;
async fn resource_name<'a>(&'a self, username: &'a String) -> anyhow::Result<ResourceName<'a>> {
Ok(ResourceName::named(username.clone()))
}
fn action() -> Self::Action {
ApiActions::DeleteUser
}
async fn handle_protected(
&self,
_permissions: &Permissions,
username: String,
) -> anyhow::Result<ApiResponse> {
let mut users = self.users.lock().await;
let old_len = users.len();
users.retain(|u| u != &username);
if old_len != users.len() {
println!("User removed.");
} else {
anyhow::bail!("User {} not found", username)
}
Ok(ApiResponse::UserDeleted)
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let users = vec![
String::from("admin"),
String::from("jane"),
String::from("jon"),
String::from("jill"),
String::from("jim"),
];
let admin_permissions = Permissions::allow_all();
let known_user_permissions =
Permissions::from(vec![Statement::for_any().allowing(&ApiActions::AddUser)]);
let default_permissions =
Permissions::from(Statement::for_resource("jon").allowing(&ApiActions::DeleteUser));
let dispatcher = Server {
users: Arc::new(Mutex::new(users)),
};
let stdin = io::stdin();
let mut lines = stdin.lock().lines();
loop {
println!("Welcome to the user service. Please enter your name:");
let name = lines.next().expect("need a name")?;
let effective_permissions = if name == "admin" {
&admin_permissions
} else {
let users = dispatcher.users.lock().await;
if users.contains(&name) {
&known_user_permissions
} else {
&default_permissions
}
};
println!(
"Hello, {}! Please enter the number of command you wish to execute:",
name
);
println!("1. List Users");
println!("2. Add User");
println!("3. Remove User");
println!("4. Exit");
let request = match lines.next().expect("no command")?.parse()? {
1u32 => ApiRequest::ListUsers,
2 => {
println!("Enter the new user's name:");
let new_user_name = lines.next().unwrap()?;
ApiRequest::AddUser(new_user_name)
}
3 => {
println!("Enter the name of the user you wish to remove:");
let username = lines.next().unwrap()?;
ApiRequest::DeleteUser { username }
}
4 => break,
other => {
println!("Unknown command number {}. Exiting.", other);
continue;
}
};
if let Err(err) = dispatcher.dispatch(effective_permissions, request).await {
println!("Error received: {:?}", err);
}
}
Ok(())
}