From 8c8b4ea488cae1bc2e55e49e62673151bb139e07 Mon Sep 17 00:00:00 2001 From: beckerinj Date: Thu, 1 Dec 2022 22:58:22 -0500 Subject: [PATCH] create permissioned delete routes --- core/src/api/build.rs | 112 +++++++++++++++++++++++++++++-------- core/src/api/deployment.rs | 49 ++++++++++++---- core/src/api/server.rs | 91 +++++++++++++++++++++++------- lib/types/readme.md | 5 ++ lib/types/src/lib.rs | 2 +- 5 files changed, 205 insertions(+), 54 deletions(-) create mode 100644 lib/types/readme.md diff --git a/core/src/api/build.rs b/core/src/api/build.rs index a9ca78b8e..9001faac6 100644 --- a/core/src/api/build.rs +++ b/core/src/api/build.rs @@ -1,45 +1,74 @@ -use anyhow::{anyhow, Context}; +use anyhow::anyhow; use async_timing_util::unix_timestamp_ms; -use axum::{routing::post, Extension, Json, Router}; +use axum::{ + extract::Path, + routing::{delete, post}, + Extension, Json, Router, +}; use db::DbExtension; use helpers::handle_anyhow_error; -use types::{Build, Operation, PermissionLevel, Update, UpdateTarget}; +use mungos::Deserialize; +use types::{traits::Permissioned, Build, Log, Operation, PermissionLevel, Update, UpdateTarget}; use crate::{auth::RequestUserExtension, ws::update}; -use super::add_update; +use super::{add_update, PeripheryExtension}; + +#[derive(Deserialize)] +struct BuildId { + id: String, +} + +#[derive(Deserialize)] +struct CreateBuildBody { + name: String, + server_id: String, +} pub fn router() -> Router { - Router::new().route( - "/create", - post(|db, user, update_ws, build| async { - create(db, user, update_ws, build) - .await - .map_err(handle_anyhow_error) - }), - ) + Router::new() + .route( + "/create", + post(|db, user, update_ws, build| async { + create(db, user, update_ws, build) + .await + .map_err(handle_anyhow_error) + }), + ) + .route( + "/delete/:id", + delete(|db, user, update_ws, periphery, build_id| async { + delete_one(db, user, update_ws, periphery, build_id) + .await + .map_err(handle_anyhow_error) + }), + ) +} + +impl Into for CreateBuildBody { + fn into(self) -> Build { + Build { + name: self.name, + server_id: self.server_id, + ..Default::default() + } + } } async fn create( Extension(db): DbExtension, Extension(user): RequestUserExtension, Extension(update_ws): update::UpdateWsSenderExtension, - Json(mut build): Json, + Json(build): Json, ) -> anyhow::Result<()> { - let build_server = db - .servers - .find_one_by_id(&build.server_id) - .await - .context("failed at query to find server")? - .ok_or(anyhow!("did not find server with server_id given on build"))?; - let permissions = *build_server.permissions.get(&user.id).ok_or(anyhow!( - "user does not have permissions to create build on this server" - ))?; - if permissions != PermissionLevel::Write { + let server = db.get_server(&build.server_id).await?; + let permissions = server.get_user_permissions(&user.id); + if !user.is_admin && permissions != PermissionLevel::Write { return Err(anyhow!( "user does not have permissions to create build on this server" )); } + let mut build: Build = build.into(); build.permissions = [(user.id.clone(), PermissionLevel::Write)] .into_iter() .collect(); @@ -55,3 +84,40 @@ async fn create( }; add_update(update, &db, &update_ws).await } + +async fn delete_one( + Extension(db): DbExtension, + Extension(user): RequestUserExtension, + Extension(update_ws): update::UpdateWsSenderExtension, + Extension(periphery): PeripheryExtension, + Path(BuildId { id }): Path, +) -> anyhow::Result<()> { + let build = db.get_build(&id).await?; + let permissions = build.get_user_permissions(&user.id); + if permissions != PermissionLevel::Write { + return Err(anyhow!( + "user does not have permissions to delete build {} ({id})", + build.name + )); + } + let start_ts = unix_timestamp_ms() as i64; + let server = db.get_server(&build.server_id).await?; + // clean up anything left by the build (delete the repo) + db.builds.delete_one(&id).await?; + let update = Update { + target: UpdateTarget::System, + operation: Operation::DeleteDeployment, + start_ts, + end_ts: Some(unix_timestamp_ms() as i64), + operator: user.id.clone(), + log: vec![ + // log, + Log::simple(format!( + "deleted build {} on server {}", + build.name, server.name + )), + ], + ..Default::default() + }; + add_update(update, &db, &update_ws).await +} diff --git a/core/src/api/deployment.rs b/core/src/api/deployment.rs index 3336ed0b2..981854fbd 100644 --- a/core/src/api/deployment.rs +++ b/core/src/api/deployment.rs @@ -1,6 +1,10 @@ use anyhow::{anyhow, Context}; use async_timing_util::unix_timestamp_ms; -use axum::{routing::post, Extension, Json, Router}; +use axum::{ + extract::Path, + routing::{delete, post}, + Extension, Json, Router, +}; use db::DbExtension; use helpers::handle_anyhow_error; use mungos::Deserialize; @@ -13,10 +17,16 @@ use crate::{auth::RequestUserExtension, ws::update}; use super::{add_update, PeripheryExtension}; #[derive(Deserialize)] -pub struct DeploymentIdBody { +pub struct DeploymentId { id: String, } +#[derive(Deserialize)] +pub struct CreateDeploymentBody { + name: String, + server_id: String, +} + pub fn router() -> Router { Router::new() .route( @@ -28,21 +38,39 @@ pub fn router() -> Router { }), ) .route( - "/delete", - post(|db, user, update_ws, periphery, deployment_id| async { - delete(db, user, update_ws, periphery, deployment_id) + "/delete/:id", + delete(|db, user, update_ws, periphery, deployment_id| async { + delete_one(db, user, update_ws, periphery, deployment_id) .await .map_err(handle_anyhow_error) }), ) } +impl Into for CreateDeploymentBody { + fn into(self) -> Deployment { + Deployment { + name: self.name, + server_id: self.server_id, + ..Default::default() + } + } +} + async fn create( Extension(db): DbExtension, Extension(user): RequestUserExtension, Extension(update_ws): update::UpdateWsSenderExtension, - Json(mut deployment): Json, + Json(deployment): Json, ) -> anyhow::Result<()> { + let server = db.get_server(&deployment.server_id).await?; + let permissions = server.get_user_permissions(&user.id); + if permissions != PermissionLevel::Write { + return Err(anyhow!( + "user does not have permissions to create deployment on this server" + )); + } + let mut deployment: Deployment = deployment.into(); deployment.permissions = [(user.id.clone(), PermissionLevel::Write)] .into_iter() .collect(); @@ -63,22 +91,23 @@ async fn create( add_update(update, &db, &update_ws).await } -async fn delete( +async fn delete_one( Extension(db): DbExtension, Extension(user): RequestUserExtension, Extension(update_ws): update::UpdateWsSenderExtension, Extension(periphery): PeripheryExtension, - Json(DeploymentIdBody { id }): Json, + Path(DeploymentId { id }): Path, ) -> anyhow::Result<()> { let deployment = db.get_deployment(&id).await?; let permissions = deployment.get_user_permissions(&user.id); if permissions != PermissionLevel::Write { return Err(anyhow!( - "user does not have permissions to delete deployment {id}" + "user does not have permissions to delete deployment {} ({id})", + deployment.name )); } - let server = db.get_server(&deployment.server_id).await?; let start_ts = unix_timestamp_ms() as i64; + let server = db.get_server(&deployment.server_id).await?; let log = periphery .container_remove(&server, &deployment.name) .await?; diff --git a/core/src/api/server.rs b/core/src/api/server.rs index 21db26c74..3b112efec 100644 --- a/core/src/api/server.rs +++ b/core/src/api/server.rs @@ -2,20 +2,32 @@ use anyhow::{anyhow, Context}; use async_timing_util::unix_timestamp_ms; use axum::{ extract::Path, - routing::{get, post}, + routing::{delete, get, post}, Extension, Json, Router, }; use db::DbExtension; use helpers::handle_anyhow_error; use mungos::Deserialize; use types::{ - traits::Permissioned, Operation, PermissionLevel, Server, SystemStats, Update, UpdateTarget, + traits::Permissioned, Log, Operation, PermissionLevel, Server, SystemStats, Update, + UpdateTarget, }; use crate::{auth::RequestUserExtension, ws::update}; use super::{add_update, PeripheryExtension}; +#[derive(Deserialize)] +struct ServerId { + id: String, +} + +#[derive(Deserialize)] +struct CreateServerBody { + name: String, + address: String, +} + pub fn router() -> Router { Router::new() .route( @@ -31,9 +43,17 @@ pub fn router() -> Router { }), ) .route( - "/stats/:server_id", - get(|db, user, periphery, path| async { - stats(db, user, periphery, path) + "/delete/:id", + delete(|db, user, update_ws, server_id| async { + delete_one(db, user, update_ws, server_id) + .await + .map_err(handle_anyhow_error) + }), + ) + .route( + "/stats/:id", + get(|db, user, periphery, server_id| async { + stats(db, user, periphery, server_id) .await .map_err(handle_anyhow_error) }), @@ -54,10 +74,7 @@ async fn list( if user.is_admin { true } else { - let permissions = *s - .permissions - .get(&user.id) - .unwrap_or(&PermissionLevel::None); + let permissions = s.get_user_permissions(&user.id); permissions != PermissionLevel::None } }) @@ -66,12 +83,28 @@ async fn list( Ok(Json(servers)) } +impl Into for CreateServerBody { + fn into(self) -> Server { + Server { + name: self.name, + address: self.address, + ..Default::default() + } + } +} + async fn create( Extension(db): DbExtension, Extension(user): RequestUserExtension, Extension(update_ws): update::UpdateWsSenderExtension, - Json(mut server): Json, + Json(server): Json, ) -> anyhow::Result<()> { + if !user.is_admin { + return Err(anyhow!( + "user does not have permissions to add server (not admin)" + )); + } + let mut server: Server = server.into(); server.permissions = [(user.id.clone(), PermissionLevel::Write)] .into_iter() .collect(); @@ -92,23 +125,41 @@ async fn create( add_update(update, &db, &update_ws).await } -#[derive(Deserialize)] -struct GetStatsPath { - server_id: String, +async fn delete_one( + Extension(db): DbExtension, + Extension(user): RequestUserExtension, + Extension(update_ws): update::UpdateWsSenderExtension, + Path(ServerId { id }): Path, +) -> anyhow::Result<()> { + let server = db.get_server(&id).await?; + let permissions = server.get_user_permissions(&user.id); + if !user.is_admin && permissions != PermissionLevel::Write { + return Err(anyhow!( + "user does not have permissions to delete server {} ({id})", + server.name + )); + } + let start_ts = unix_timestamp_ms() as i64; + db.deployments.delete_one(&id).await?; + let update = Update { + target: UpdateTarget::System, + operation: Operation::DeleteServer, + start_ts, + end_ts: Some(unix_timestamp_ms() as i64), + operator: user.id.clone(), + log: vec![Log::simple(format!("deleted server {}", server.name))], + ..Default::default() + }; + add_update(update, &db, &update_ws).await } async fn stats( Extension(db): DbExtension, Extension(user): RequestUserExtension, Extension(periphery): PeripheryExtension, - Path(GetStatsPath { server_id }): Path, + Path(ServerId { id }): Path, ) -> anyhow::Result> { - let server = db - .servers - .find_one_by_id(&server_id) - .await - .context("failed at query to get server")? - .ok_or(anyhow!("failed to find server with id {server_id}"))?; + let server = db.get_server(&id).await?; let permissions = server.get_user_permissions(&user.id); if permissions == PermissionLevel::None { return Err(anyhow!("user does not have permissions on this server")); diff --git a/lib/types/readme.md b/lib/types/readme.md new file mode 100644 index 000000000..19f497210 --- /dev/null +++ b/lib/types/readme.md @@ -0,0 +1,5 @@ +# monitor types + +## these are the types used with the [monitor deployment system](https://github.com/mbecker20/monitor) + +### these are a dependency of [monitor_client_rs](https://crates.io/crates/monitor_client_rs) crate and are reexported by that as well, so these usually don't need to be added as a standalone crate diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 4f512e927..a9f9bb1bb 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -99,7 +99,7 @@ fn default_disk_alert() -> f64 { 75.0 } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct Deployment { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option,