From c274fa657714df56ab3b8092f623dafecb52d2ed Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Mon, 5 May 2025 20:18:49 +0200 Subject: [PATCH 1/2] feat: Add user permissions --- controller/src/application/auth.rs | 5 +- controller/src/application/auth/manager.rs | 34 +- .../src/application/auth/permissions.rs | 40 +- controller/src/application/auth/server.rs | 7 +- controller/src/application/auth/user.rs | 20 +- controller/src/config.rs | 3 +- controller/src/network/manage.rs | 611 ++++++++++-------- controller/src/task/network.rs | 27 +- 8 files changed, 453 insertions(+), 294 deletions(-) diff --git a/controller/src/application/auth.rs b/controller/src/application/auth.rs index 42a4e632..60812cd6 100644 --- a/controller/src/application/auth.rs +++ b/controller/src/application/auth.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use permissions::Permissions; use server::AuthServer; use user::AdminUser; @@ -10,6 +11,7 @@ pub mod server; pub mod user; const DEFAULT_ADMIN_USERNAME: &str = "admin"; +const DEFAULT_ADMIN_PERMISSIONS: Permissions = Permissions::ALL; pub type AuthToken = String; pub type OwnedAuthorization = Box; @@ -21,8 +23,7 @@ pub trait GenericAuthorization { fn get_user(&self) -> Option<&AdminUser>; fn is_type(&self, auth: AuthType) -> bool; - #[allow(unused)] - fn is_allowed(&self, flag: u32) -> bool; + fn is_allowed(&self, flag: Permissions) -> bool; fn recreate(&self) -> OwnedAuthorization; } diff --git a/controller/src/application/auth/manager.rs b/controller/src/application/auth/manager.rs index ee8c52b9..59701ab3 100644 --- a/controller/src/application/auth/manager.rs +++ b/controller/src/application/auth/manager.rs @@ -7,7 +7,9 @@ use tokio::{fs, sync::RwLock}; use uuid::Uuid; use crate::{ - application::auth::DEFAULT_ADMIN_USERNAME, + application::auth::{ + permissions::Permissions, DEFAULT_ADMIN_PERMISSIONS, DEFAULT_ADMIN_USERNAME, + }, storage::{SaveToTomlFile, Storage}, }; @@ -34,11 +36,15 @@ impl AuthManager { .await? { info!("Loaded user {}", name); - tokens.insert(value.token().clone(), AdminUser::create(name)); + tokens.insert( + value.token().clone(), + AdminUser::create(name, value.permissions().clone()), + ); } if tokens.is_empty() { - let token = Self::create_user(DEFAULT_ADMIN_USERNAME).await?; + let token = + Self::create_user(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PERMISSIONS).await?; info!("-----------------------------------"); info!("No users found, created default admin user"); info!("Username: {}", DEFAULT_ADMIN_USERNAME); @@ -46,7 +52,13 @@ impl AuthManager { info!("-----------------------------------"); info!("Welcome to Atomic Cloud"); info!("-----------------------------------"); - tokens.insert(token, AdminUser::create(DEFAULT_ADMIN_USERNAME.to_string())); + tokens.insert( + token, + AdminUser::create( + DEFAULT_ADMIN_USERNAME.to_string(), + DEFAULT_ADMIN_PERMISSIONS, + ), + ); } info!("Loaded {} user(s)", tokens.len()); @@ -82,13 +94,13 @@ impl AuthManager { token } - async fn create_user(username: &str) -> Result { + async fn create_user(username: &str, permissions: Permissions) -> Result { let token = format!( "actl_{}{}", Uuid::new_v4().as_simple(), Uuid::new_v4().as_simple() ); - StoredUser::new(&token) + StoredUser::new(&token, permissions) .save(&Storage::user_file(username), true) .await?; @@ -102,21 +114,27 @@ mod stored { use getset::Getters; use serde::{Deserialize, Serialize}; - use crate::storage::{LoadFromTomlFile, SaveToTomlFile}; + use crate::{ + application::auth::permissions::Permissions, + storage::{LoadFromTomlFile, SaveToTomlFile}, + }; #[derive(Serialize, Deserialize, Getters)] pub struct StoredUser { #[getset(get = "pub")] token: String, + #[getset(get = "pub")] + permissions: Permissions, } impl StoredUser { - pub fn new<'a, T>(token: T) -> Self + pub fn new<'a, T>(token: T, permissions: Permissions) -> Self where T: Into>, { Self { token: token.into().into_owned(), + permissions, } } } diff --git a/controller/src/application/auth/permissions.rs b/controller/src/application/auth/permissions.rs index 5c15722c..108b69a0 100644 --- a/controller/src/application/auth/permissions.rs +++ b/controller/src/application/auth/permissions.rs @@ -1,14 +1,38 @@ use bitflags::bitflags; +use serde::{Deserialize, Serialize}; bitflags! { + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] + #[serde(transparent)] pub struct Permissions: u32 { - const READ = 1; - const WRITE = 1 << 1; - const EXECUTE = 1 << 2; - const DELETE = 1 << 3; - const CREATE = 1 << 4; - const MODIFY = 1 << 5; - const LIST = 1 << 6; - const ALL = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits() | Self::DELETE.bits() | Self::CREATE.bits() | Self::MODIFY.bits() | Self::LIST.bits(); + const REQUEST_STOP = 1; + + const SET_RESOURCE = 1 << 1; + const DELETE_RESOURCE = 1 << 2; + + const CREATE_NODE = 1 << 3; + const UPDATE_NODE = 1 << 4; + const GET_NODE = 1 << 5; + + const CREATE_GROUP = 1 << 6; + const UPDATE_GROUP = 1 << 7; + const GET_GROUP = 1 << 8; + + const SCHEDULE_SERVER = 1 << 9; + const GET_SERVER = 1 << 10; + + const WRITE_TO_SCREEN = 1 << 11; + const READ_SCREEN = 1 << 12; + + const GET_USER = 1 << 13; + + const TRANSFER_USER = 1 << 14; + + const READ_POWER_EVENTS = 1 << 15; + const READ_READY_EVENTS = 1 << 16; + + const LIST = 1 << 17; + + const ALL = Self::REQUEST_STOP.bits() | Self::SET_RESOURCE.bits() | Self::DELETE_RESOURCE.bits() | Self::CREATE_NODE.bits() | Self::UPDATE_NODE.bits() | Self::GET_NODE.bits() | Self::CREATE_GROUP.bits() | Self::UPDATE_GROUP.bits() | Self::GET_GROUP.bits() | Self::SCHEDULE_SERVER.bits() | Self::GET_SERVER.bits() | Self::WRITE_TO_SCREEN.bits() | Self::READ_SCREEN.bits() | Self::GET_USER.bits() | Self::TRANSFER_USER.bits() | Self::READ_POWER_EVENTS.bits() | Self::READ_READY_EVENTS.bits() | Self::LIST.bits(); } } diff --git a/controller/src/application/auth/server.rs b/controller/src/application/auth/server.rs index 085a8846..164db71d 100644 --- a/controller/src/application/auth/server.rs +++ b/controller/src/application/auth/server.rs @@ -1,7 +1,9 @@ use getset::Getters; use uuid::Uuid; -use super::{user::AdminUser, AuthType, GenericAuthorization, OwnedAuthorization}; +use super::{ + permissions::Permissions, user::AdminUser, AuthType, GenericAuthorization, OwnedAuthorization, +}; #[derive(Getters)] pub struct AuthServer { @@ -10,7 +12,8 @@ pub struct AuthServer { } impl GenericAuthorization for AuthServer { - fn is_allowed(&self, _flag: u32) -> bool { + fn is_allowed(&self, _flag: Permissions) -> bool { + // Server are allowed to do everything in there extra "client" gRPC area true } diff --git a/controller/src/application/auth/user.rs b/controller/src/application/auth/user.rs index 27b9a271..56befd26 100644 --- a/controller/src/application/auth/user.rs +++ b/controller/src/application/auth/user.rs @@ -1,16 +1,21 @@ use getset::Getters; -use super::{server::AuthServer, AuthType, GenericAuthorization, OwnedAuthorization}; +use super::{ + permissions::Permissions, server::AuthServer, AuthType, GenericAuthorization, + OwnedAuthorization, +}; #[derive(Getters)] pub struct AdminUser { #[getset(get = "pub")] username: String, + #[getset(get = "pub")] + permissions: Permissions, } impl GenericAuthorization for AdminUser { - fn is_allowed(&self, _flag: u32) -> bool { - true + fn is_allowed(&self, flag: Permissions) -> bool { + self.permissions.contains(flag) } fn get_user(&self) -> Option<&AdminUser> { @@ -24,12 +29,15 @@ impl GenericAuthorization for AdminUser { } fn recreate(&self) -> OwnedAuthorization { - AdminUser::create(self.username.clone()) + AdminUser::create(self.username.clone(), self.permissions.clone()) } } impl AdminUser { - pub fn create(username: String) -> OwnedAuthorization { - Box::new(Self { username }) + pub fn create(username: String, permissions: Permissions) -> OwnedAuthorization { + Box::new(Self { + username, + permissions, + }) } } diff --git a/controller/src/config.rs b/controller/src/config.rs index 645af1d5..053bb13a 100644 --- a/controller/src/config.rs +++ b/controller/src/config.rs @@ -3,6 +3,7 @@ use std::{net::SocketAddr, time::Duration}; use anyhow::Result; use serde::Deserialize; use tokio::fs; +use uuid::Uuid; use crate::storage::{LoadFromTomlFile, Storage}; @@ -46,7 +47,7 @@ impl Config { if let Some(parent) = path.parent() { fs::create_dir_all(parent).await?; } - fs::write(&path, DEFAULT_CONFIG).await?; + fs::write(&path, DEFAULT_CONFIG.replace("%RANDOM%", &Uuid::new_v4().to_string())).await?; Self::from_file(&path).await } } diff --git a/controller/src/network/manage.rs b/controller/src/network/manage.rs index 259d2069..5a40fa2d 100644 --- a/controller/src/network/manage.rs +++ b/controller/src/network/manage.rs @@ -15,7 +15,7 @@ use uuid::Uuid; use crate::{ application::{ - auth::AuthType, + auth::{permissions::Permissions, AuthType}, group::{ScalingPolicy, StartConstraints}, node::Capabilities, server::{DiskRetention, FallbackPolicy, Resources, Specification}, @@ -23,7 +23,10 @@ use crate::{ user::transfer::TransferTarget, Shared, }, - task::{manager::TaskSender, network::TonicTask}, + task::{ + manager::TaskSender, + network::{TonicTask, INSUFFICIENT_PERMISSIONS_MESSAGE}, + }, VERSION, }; @@ -63,9 +66,13 @@ impl ManageService for ManageServiceImpl { // Power async fn request_stop(&self, request: Request<()>) -> Result, Status> { Ok(Response::new( - TonicTask::execute::<(), _, _>(AuthType::User, &self.0, request, |_, _| { - Ok(Box::new(RequestStopTask())) - }) + TonicTask::execute_authorized::<(), _, _>( + AuthType::User, + Permissions::REQUEST_STOP, + &self.0, + request, + |_, _| Ok(Box::new(RequestStopTask())), + ) .await?, )) } @@ -73,33 +80,45 @@ impl ManageService for ManageServiceImpl { // Resource async fn set_resource(&self, request: Request) -> Result, Status> { Ok(Response::new( - TonicTask::execute::<(), _, _>(AuthType::User, &self.0, request, |request, _| { - let request = request.into_inner(); - - let Ok(category) = Category::try_from(request.category) else { - return Err(Status::invalid_argument("Invalid category provided")); - }; - - Ok(Box::new(SetResourceTask( - category, - request.id, - request.active, - ))) - }) + TonicTask::execute_authorized::<(), _, _>( + AuthType::User, + Permissions::SET_RESOURCE, + &self.0, + request, + |request, _| { + let request = request.into_inner(); + + let Ok(category) = Category::try_from(request.category) else { + return Err(Status::invalid_argument("Invalid category provided")); + }; + + Ok(Box::new(SetResourceTask( + category, + request.id, + request.active, + ))) + }, + ) .await?, )) } async fn delete_resource(&self, request: Request) -> Result, Status> { Ok(Response::new( - TonicTask::execute::<(), _, _>(AuthType::User, &self.0, request, |request, _| { - let request = request.into_inner(); + TonicTask::execute_authorized::<(), _, _>( + AuthType::User, + Permissions::DELETE_RESOURCE, + &self.0, + request, + |request, _| { + let request = request.into_inner(); - let Ok(category) = Category::try_from(request.category) else { - return Err(Status::invalid_argument("Invalid category provided")); - }; + let Ok(category) = Category::try_from(request.category) else { + return Err(Status::invalid_argument("Invalid category provided")); + }; - Ok(Box::new(DeleteResourceTask(category, request.id))) - }) + Ok(Box::new(DeleteResourceTask(category, request.id))) + }, + ) .await?, )) } @@ -110,8 +129,9 @@ impl ManageService for ManageServiceImpl { request: Request<()>, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::LIST, &self.0, request, |_, _| Ok(Box::new(GetPluginsTask())), @@ -126,30 +146,35 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::<(), _, _>(AuthType::User, &self.0, request, |request, _| { - let request = request.into_inner(); - - let capabilities = match request.capabilities { - Some(capabilities) => Capabilities::new( - capabilities.memory, - capabilities.servers, - capabilities.child_node, - ), - None => return Err(Status::invalid_argument("No capabilities provided")), - }; - let controller = request - .controller_address - .parse() - .map_err(|_| Status::invalid_argument("Invalid controller address provided"))?; - let plugin = request.plugin; - - Ok(Box::new(CreateNodeTask( - request.name, - plugin, - capabilities, - controller, - ))) - }) + TonicTask::execute_authorized::<(), _, _>( + AuthType::User, + Permissions::CREATE_NODE, + &self.0, + request, + |request, _| { + let request = request.into_inner(); + + let capabilities = match request.capabilities { + Some(capabilities) => Capabilities::new( + capabilities.memory, + capabilities.servers, + capabilities.child_node, + ), + None => return Err(Status::invalid_argument("No capabilities provided")), + }; + let controller = request.controller_address.parse().map_err(|_| { + Status::invalid_argument("Invalid controller address provided") + })?; + let plugin = request.plugin; + + Ok(Box::new(CreateNodeTask( + request.name, + plugin, + capabilities, + controller, + ))) + }, + ) .await?, )) } @@ -158,8 +183,9 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::UPDATE_NODE, &self.0, request, |request, _| { @@ -205,8 +231,9 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::GET_NODE, &self.0, request, |request, _| { @@ -223,8 +250,9 @@ impl ManageService for ManageServiceImpl { request: Request<()>, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::LIST, &self.0, request, |_, _| Ok(Box::new(GetNodesTask())), @@ -239,98 +267,104 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::<(), _, _>(AuthType::User, &self.0, request, |request, _| { - let request = request.into_inner(); - - let constraints = match request.constraints { - Some(constrains) => StartConstraints::new( - constrains.min_servers, - constrains.max_servers, - constrains.priority, - ), - None => return Err(Status::invalid_argument("No constraints provided")), - }; - - let scaling = match request.scaling { - Some(scaling) => ScalingPolicy::new( - scaling.enabled, - scaling.start_threshold, - scaling.stop_empty, - ), - None => return Err(Status::invalid_argument("No scaling policy provided")), - }; - - let resources = match request.resources { - Some(resources) => Resources::new( - resources.memory, - resources.swap, - resources.cpu, - resources.io, - resources.disk, - resources.ports, - ), - None => return Err(Status::invalid_argument("No resources provided")), - }; - - let specification = match request.specification { - Some(specification) => { - let image = specification.image; - let max_players = specification.max_players; - let settings = specification - .settings - .iter() - .map(|key_value| (key_value.key.clone(), key_value.value.clone())) - .collect(); - let environment = specification - .environment - .iter() - .map(|key_value| (key_value.key.clone(), key_value.value.clone())) - .collect(); - let disk_retention = if let Some(retention) = specification.retention { - match manage::server::DiskRetention::try_from(retention) { - Ok(manage::server::DiskRetention::Permanent) => { - DiskRetention::Permanent - } - Ok(manage::server::DiskRetention::Temporary) => { - DiskRetention::Temporary - } - Err(_) => { - return Err(Status::invalid_argument( - "Invalid disk retention provided", - )) + TonicTask::execute_authorized::<(), _, _>( + AuthType::User, + Permissions::CREATE_GROUP, + &self.0, + request, + |request, _| { + let request = request.into_inner(); + + let constraints = match request.constraints { + Some(constrains) => StartConstraints::new( + constrains.min_servers, + constrains.max_servers, + constrains.priority, + ), + None => return Err(Status::invalid_argument("No constraints provided")), + }; + + let scaling = match request.scaling { + Some(scaling) => ScalingPolicy::new( + scaling.enabled, + scaling.start_threshold, + scaling.stop_empty, + ), + None => return Err(Status::invalid_argument("No scaling policy provided")), + }; + + let resources = match request.resources { + Some(resources) => Resources::new( + resources.memory, + resources.swap, + resources.cpu, + resources.io, + resources.disk, + resources.ports, + ), + None => return Err(Status::invalid_argument("No resources provided")), + }; + + let specification = match request.specification { + Some(specification) => { + let image = specification.image; + let max_players = specification.max_players; + let settings = specification + .settings + .iter() + .map(|key_value| (key_value.key.clone(), key_value.value.clone())) + .collect(); + let environment = specification + .environment + .iter() + .map(|key_value| (key_value.key.clone(), key_value.value.clone())) + .collect(); + let disk_retention = if let Some(retention) = specification.retention { + match manage::server::DiskRetention::try_from(retention) { + Ok(manage::server::DiskRetention::Permanent) => { + DiskRetention::Permanent + } + Ok(manage::server::DiskRetention::Temporary) => { + DiskRetention::Temporary + } + Err(_) => { + return Err(Status::invalid_argument( + "Invalid disk retention provided", + )) + } } - } - } else { - DiskRetention::Temporary - }; - let fallback = if let Some(fallback) = specification.fallback { - FallbackPolicy::new(true, fallback.priority) - } else { - FallbackPolicy::default() - }; - Specification::new( - settings, - environment, - disk_retention, - image, - max_players, - fallback, - ) - } - None => return Err(Status::invalid_argument("No specification provided")), - }; - - let nodes = request.nodes; - - Ok(Box::new(CreateGroupTask( - request.name, - constraints, - scaling, - resources, - specification, - nodes, - ))) - }) + } else { + DiskRetention::Temporary + }; + let fallback = if let Some(fallback) = specification.fallback { + FallbackPolicy::new(true, fallback.priority) + } else { + FallbackPolicy::default() + }; + Specification::new( + settings, + environment, + disk_retention, + image, + max_players, + fallback, + ) + } + None => return Err(Status::invalid_argument("No specification provided")), + }; + + let nodes = request.nodes; + + Ok(Box::new(CreateGroupTask( + request.name, + constraints, + scaling, + resources, + specification, + nodes, + ))) + }, + ) .await?, )) } @@ -339,8 +373,9 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::UPDATE_GROUP, &self.0, request, |request, _| { @@ -437,8 +472,9 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::GET_GROUP, &self.0, request, |request, _| Ok(Box::new(GetGroupTask(request.into_inner()))), @@ -451,8 +487,9 @@ impl ManageService for ManageServiceImpl { request: Request<()>, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::LIST, &self.0, request, |_, _| Ok(Box::new(GetGroupsTask)), @@ -467,77 +504,83 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::(AuthType::User, &self.0, request, |request, _| { - let request = request.into_inner(); - - let resources = match request.resources { - Some(resources) => Resources::new( - resources.memory, - resources.swap, - resources.cpu, - resources.io, - resources.disk, - resources.ports, - ), - None => return Err(Status::invalid_argument("No resources provided")), - }; - - let specification = match request.specification { - Some(specification) => { - let image = specification.image; - let max_players = specification.max_players; - let settings = specification - .settings - .iter() - .map(|key_value| (key_value.key.clone(), key_value.value.clone())) - .collect(); - let environment = specification - .environment - .iter() - .map(|key_value| (key_value.key.clone(), key_value.value.clone())) - .collect(); - let disk_retention = if let Some(retention) = specification.retention { - match manage::server::DiskRetention::try_from(retention) { - Ok(manage::server::DiskRetention::Permanent) => { - DiskRetention::Permanent - } - Ok(manage::server::DiskRetention::Temporary) => { - DiskRetention::Temporary - } - Err(_) => { - return Err(Status::invalid_argument( - "Invalid disk retention provided", - )) + TonicTask::execute_authorized::( + AuthType::User, + Permissions::SCHEDULE_SERVER, + &self.0, + request, + |request, _| { + let request = request.into_inner(); + + let resources = match request.resources { + Some(resources) => Resources::new( + resources.memory, + resources.swap, + resources.cpu, + resources.io, + resources.disk, + resources.ports, + ), + None => return Err(Status::invalid_argument("No resources provided")), + }; + + let specification = match request.specification { + Some(specification) => { + let image = specification.image; + let max_players = specification.max_players; + let settings = specification + .settings + .iter() + .map(|key_value| (key_value.key.clone(), key_value.value.clone())) + .collect(); + let environment = specification + .environment + .iter() + .map(|key_value| (key_value.key.clone(), key_value.value.clone())) + .collect(); + let disk_retention = if let Some(retention) = specification.retention { + match manage::server::DiskRetention::try_from(retention) { + Ok(manage::server::DiskRetention::Permanent) => { + DiskRetention::Permanent + } + Ok(manage::server::DiskRetention::Temporary) => { + DiskRetention::Temporary + } + Err(_) => { + return Err(Status::invalid_argument( + "Invalid disk retention provided", + )) + } } - } - } else { - DiskRetention::Temporary - }; - let fallback = if let Some(fallback) = specification.fallback { - FallbackPolicy::new(true, fallback.priority) - } else { - FallbackPolicy::default() - }; - Specification::new( - settings, - environment, - disk_retention, - image, - max_players, - fallback, - ) - } - None => return Err(Status::invalid_argument("No spec provided")), - }; - - Ok(Box::new(ScheduleServerTask( - request.prio, - request.name.clone(), - request.node, - resources, - specification, - ))) - }) + } else { + DiskRetention::Temporary + }; + let fallback = if let Some(fallback) = specification.fallback { + FallbackPolicy::new(true, fallback.priority) + } else { + FallbackPolicy::default() + }; + Specification::new( + settings, + environment, + disk_retention, + image, + max_players, + fallback, + ) + } + None => return Err(Status::invalid_argument("No spec provided")), + }; + + Ok(Box::new(ScheduleServerTask( + request.prio, + request.name.clone(), + request.node, + resources, + specification, + ))) + }, + ) .await?, )) } @@ -546,8 +589,9 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::GET_SERVER, &self.0, request, |request, _| { @@ -568,8 +612,9 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::GET_SERVER, &self.0, request, |request, _| { @@ -586,8 +631,9 @@ impl ManageService for ManageServiceImpl { request: Request<()>, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::LIST, &self.0, request, |_, _| Ok(Box::new(GetServersTask)), @@ -598,6 +644,11 @@ impl ManageService for ManageServiceImpl { // Screen async fn write_to_screen(&self, request: Request) -> Result, Status> { + let auth = TonicTask::get_auth(AuthType::User, &request)?; + if !auth.is_allowed(Permissions::WRITE_TO_SCREEN) { + return Err(Status::permission_denied(INSUFFICIENT_PERMISSIONS_MESSAGE)); + } + let request = request.into_inner(); let Ok(uuid) = Uuid::from_str(&request.id) else { return Err(Status::invalid_argument("Invalid UUID provided")); @@ -613,6 +664,11 @@ impl ManageService for ManageServiceImpl { &self, request: Request, ) -> Result, Status> { + let auth = TonicTask::get_auth(AuthType::User, &request)?; + if !auth.is_allowed(Permissions::READ_SCREEN) { + return Err(Status::permission_denied(INSUFFICIENT_PERMISSIONS_MESSAGE)); + } + let request = request.into_inner(); let Ok(uuid) = Uuid::from_str(&request) else { @@ -628,8 +684,9 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::GET_USER, &self.0, request, |request, _| { @@ -650,8 +707,9 @@ impl ManageService for ManageServiceImpl { request: Request, ) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::GET_USER, &self.0, request, |request, _| { @@ -665,8 +723,9 @@ impl ManageService for ManageServiceImpl { } async fn get_users(&self, request: Request<()>) -> Result, Status> { Ok(Response::new( - TonicTask::execute::( + TonicTask::execute_authorized::( AuthType::User, + Permissions::LIST, &self.0, request, |_, _| Ok(Box::new(GetUsersTask)), @@ -676,9 +735,13 @@ impl ManageService for ManageServiceImpl { } async fn get_user_count(&self, request: Request<()>) -> Result, Status> { Ok(Response::new( - TonicTask::execute::(AuthType::User, &self.0, request, |_, _| { - Ok(Box::new(UserCountTask)) - }) + TonicTask::execute_authorized::( + AuthType::User, + Permissions::LIST, + &self.0, + request, + |_, _| Ok(Box::new(UserCountTask)), + ) .await?, )) } @@ -686,47 +749,55 @@ impl ManageService for ManageServiceImpl { // Transfer async fn transfer_users(&self, request: Request) -> Result, Status> { Ok(Response::new( - TonicTask::execute::(AuthType::User, &self.0, request, |request, auth| { - let request = request.into_inner(); - - let target = match request.target { - Some(target) => match Type::try_from(target.r#type) { - Ok(r#type) => match (target.target, r#type) { - (Some(target), Type::Group) => TransferTarget::Group(target), - (Some(target), Type::Server) => { - TransferTarget::Server(match Uuid::from_str(&target) { - Ok(uuid) => uuid, - Err(_) => { - return Err(Status::invalid_argument( - "Invalid UUID provided", - )) - } - }) - } - (None, Type::Fallback) => TransferTarget::Fallback, - _ => { + TonicTask::execute_authorized::( + AuthType::User, + Permissions::TRANSFER_USER, + &self.0, + request, + |request, auth| { + let request = request.into_inner(); + + let target = match request.target { + Some(target) => match Type::try_from(target.r#type) { + Ok(r#type) => match (target.target, r#type) { + (Some(target), Type::Group) => TransferTarget::Group(target), + (Some(target), Type::Server) => { + TransferTarget::Server(match Uuid::from_str(&target) { + Ok(uuid) => uuid, + Err(_) => { + return Err(Status::invalid_argument( + "Invalid UUID provided", + )) + } + }) + } + (None, Type::Fallback) => TransferTarget::Fallback, + _ => { + return Err(Status::invalid_argument( + "Invalid target type combination", + )) + } + }, + Err(_) => { return Err(Status::invalid_argument( - "Invalid target type combination", + "Invalid target type provided", )) } }, - Err(_) => { - return Err(Status::invalid_argument("Invalid target type provided")) - } - }, - None => return Err(Status::invalid_argument("Missing target")), - }; - let uuids = request - .ids - .into_iter() - .map(|id| match Uuid::from_str(&id) { - Ok(uuid) => Ok(uuid), - Err(_) => Err(Status::invalid_argument("Invalid UUID provided")), - }) - .collect::, _>>()?; - - Ok(Box::new(TransferUsersTask(auth, uuids, target))) - }) + None => return Err(Status::invalid_argument("Missing target")), + }; + let uuids = request + .ids + .into_iter() + .map(|id| match Uuid::from_str(&id) { + Ok(uuid) => Ok(uuid), + Err(_) => Err(Status::invalid_argument("Invalid UUID provided")), + }) + .collect::, _>>()?; + + Ok(Box::new(TransferUsersTask(auth, uuids, target))) + }, + ) .await?, )) } @@ -742,8 +813,13 @@ impl ManageService for ManageServiceImpl { // Notify operations async fn subscribe_to_power_events( &self, - _request: Request<()>, + request: Request<()>, ) -> Result, Status> { + let auth = TonicTask::get_auth(AuthType::User, &request)?; + if !auth.is_allowed(Permissions::READ_POWER_EVENTS) { + return Err(Status::permission_denied(INSUFFICIENT_PERMISSIONS_MESSAGE)); + } + let (sender, receiver) = Subscriber::create_network(); self.1.subscribers.network().power().subscribe(sender).await; @@ -751,8 +827,13 @@ impl ManageService for ManageServiceImpl { } async fn subscribe_to_ready_events( &self, - _request: Request<()>, + request: Request<()>, ) -> Result, Status> { + let auth = TonicTask::get_auth(AuthType::User, &request)?; + if !auth.is_allowed(Permissions::READ_READY_EVENTS) { + return Err(Status::permission_denied(INSUFFICIENT_PERMISSIONS_MESSAGE)); + } + let (sender, receiver) = Subscriber::create_network(); self.1.subscribers.network().ready().subscribe(sender).await; diff --git a/controller/src/task/network.rs b/controller/src/task/network.rs index 34b09a16..a8bac838 100644 --- a/controller/src/task/network.rs +++ b/controller/src/task/network.rs @@ -9,12 +9,15 @@ use tokio::sync::oneshot::channel; use tonic::{Request, Status}; use crate::{ - application::auth::{AuthType, Authorization}, + application::auth::{permissions::Permissions, AuthType, Authorization}, task::Task, }; use super::{manager::TaskSender, BoxedAny, BoxedTask}; +pub const INSUFFICIENT_PERMISSIONS_MESSAGE: &str = + "Insufficient permissions to perform this action"; + pub struct TonicTask; impl TonicTask { @@ -22,10 +25,30 @@ impl TonicTask { pub fn get_auth(auth: AuthType, request: &Request) -> Result { match request.extensions().get::() { Some(data) if data.is_type(auth) => Ok(data.clone()), - _ => Err(Status::permission_denied("Not linked")), + _ => Err(Status::unauthenticated("Not linked")), } } + pub async fn execute_authorized( + auth: AuthType, + flag: Permissions, + queue: &TaskSender, + request: Request, + task: F, + ) -> Result + where + F: FnOnce(Request, Authorization) -> Result, + { + Self::execute(auth, queue, request, |request, auth| { + if auth.is_allowed(flag) { + task(request, auth) + } else { + Err(Status::permission_denied(INSUFFICIENT_PERMISSIONS_MESSAGE)) + } + }) + .await + } + pub async fn execute( auth: AuthType, queue: &TaskSender, From d22653302a312189dc1b6fceb44b3dc4b726d959 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Mon, 5 May 2025 20:19:11 +0200 Subject: [PATCH 2/2] fix: Clippy --- controller/src/config.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/controller/src/config.rs b/controller/src/config.rs index 053bb13a..0e13ba8b 100644 --- a/controller/src/config.rs +++ b/controller/src/config.rs @@ -47,7 +47,11 @@ impl Config { if let Some(parent) = path.parent() { fs::create_dir_all(parent).await?; } - fs::write(&path, DEFAULT_CONFIG.replace("%RANDOM%", &Uuid::new_v4().to_string())).await?; + fs::write( + &path, + DEFAULT_CONFIG.replace("%RANDOM%", &Uuid::new_v4().to_string()), + ) + .await?; Self::from_file(&path).await } }