diff --git a/bin/core/src/api/read/alerter.rs b/bin/core/src/api/read/alerter.rs index 7c7d552a7..f93eef42a 100644 --- a/bin/core/src/api/read/alerter.rs +++ b/bin/core/src/api/read/alerter.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use anyhow::Context; use async_trait::async_trait; use monitor_client::{ @@ -6,14 +8,19 @@ use monitor_client::{ alerter::{Alerter, AlerterListItem}, permission::PermissionLevel, resource::AddFilters, + update::ResourceTargetVariant, user::User, }, }; -use mungos::mongodb::bson::{doc, Document}; +use mungos::mongodb::bson::{doc, oid::ObjectId, Document}; use resolver_api::Resolve; use crate::{ - db::db_client, helpers::resource::StateResource, state::State, + db::db_client, + helpers::resource::{ + get_resource_ids_for_non_admin, StateResource, + }, + state::State, }; #[async_trait] @@ -59,8 +66,16 @@ impl Resolve for State { let query = if user.admin { None } else { + let ids = get_resource_ids_for_non_admin( + &user.id, + ResourceTargetVariant::Alerter, + ) + .await? + .into_iter() + .flat_map(|id| ObjectId::from_str(&id)) + .collect::>(); let query = doc! { - format!("permissions.{}", user.id): { "$in": ["read", "execute", "update"] } + "_id": { "$in": ids } }; Some(query) }; diff --git a/bin/core/src/api/read/build.rs b/bin/core/src/api/read/build.rs index f27cc40c1..4c31fa558 100644 --- a/bin/core/src/api/read/build.rs +++ b/bin/core/src/api/read/build.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::OnceLock}; +use std::{collections::HashMap, str::FromStr, sync::OnceLock}; use anyhow::Context; use async_timing_util::unix_timestamp_ms; @@ -10,7 +10,7 @@ use monitor_client::{ build::{Build, BuildActionState, BuildListItem}, permission::PermissionLevel, resource::AddFilters, - update::UpdateStatus, + update::{ResourceTargetVariant, UpdateStatus}, user::User, Operation, }, @@ -18,7 +18,7 @@ use monitor_client::{ use mungos::{ find::find_collect, mongodb::{ - bson::{doc, Document}, + bson::{doc, oid::ObjectId, Document}, options::FindOptions, }, }; @@ -27,7 +27,9 @@ use resolver_api::{Resolve, ResolveToString}; use crate::{ config::core_config, db::db_client, - helpers::resource::StateResource, + helpers::resource::{ + get_resource_ids_for_non_admin, StateResource, + }, state::{action_states, State}, }; @@ -94,8 +96,16 @@ impl Resolve for State { let query = if user.admin { None } else { + let ids = get_resource_ids_for_non_admin( + &user.id, + ResourceTargetVariant::Build, + ) + .await? + .into_iter() + .flat_map(|id| ObjectId::from_str(&id)) + .collect::>(); let query = doc! { - format!("permissions.{}", user.id): { "$in": ["read", "execute", "update"] } + "_id": { "$in": ids } }; Some(query) }; diff --git a/bin/core/src/api/read/builder.rs b/bin/core/src/api/read/builder.rs index a7f1c721a..bc1a9292c 100644 --- a/bin/core/src/api/read/builder.rs +++ b/bin/core/src/api/read/builder.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use anyhow::Context; use async_trait::async_trait; use monitor_client::{ @@ -6,14 +8,19 @@ use monitor_client::{ builder::{Builder, BuilderConfig, BuilderListItem}, permission::PermissionLevel, resource::AddFilters, + update::ResourceTargetVariant, user::User, }, }; -use mungos::mongodb::bson::{doc, Document}; +use mungos::mongodb::bson::{doc, oid::ObjectId, Document}; use resolver_api::Resolve; use crate::{ - db::db_client, helpers::resource::StateResource, state::State, + db::db_client, + helpers::resource::{ + get_resource_ids_for_non_admin, StateResource, + }, + state::State, }; #[async_trait] @@ -59,8 +66,16 @@ impl Resolve for State { let query = if user.admin { None } else { + let ids = get_resource_ids_for_non_admin( + &user.id, + ResourceTargetVariant::Builder, + ) + .await? + .into_iter() + .flat_map(|id| ObjectId::from_str(&id)) + .collect::>(); let query = doc! { - format!("permissions.{}", user.id): { "$in": ["read", "execute", "update"] } + "_id": { "$in": ids } }; Some(query) }; diff --git a/bin/core/src/api/read/deployment.rs b/bin/core/src/api/read/deployment.rs index 2eb606d94..36e01b148 100644 --- a/bin/core/src/api/read/deployment.rs +++ b/bin/core/src/api/read/deployment.rs @@ -1,4 +1,4 @@ -use std::cmp; +use std::{cmp, str::FromStr}; use anyhow::{anyhow, Context}; use async_trait::async_trait; @@ -13,7 +13,7 @@ use monitor_client::{ permission::PermissionLevel, resource::AddFilters, server::Server, - update::{Log, UpdateStatus}, + update::{Log, ResourceTargetVariant, UpdateStatus}, user::User, Operation, }, @@ -21,7 +21,7 @@ use monitor_client::{ use mungos::{ find::find_collect, mongodb::{ - bson::{doc, Document}, + bson::{doc, oid::ObjectId, Document}, options::FindOneOptions, }, }; @@ -31,8 +31,9 @@ use resolver_api::Resolve; use crate::{ db::db_client, helpers::{ - cache::deployment_status_cache, periphery_client, - resource::StateResource, + cache::deployment_status_cache, + periphery_client, + resource::{get_resource_ids_for_non_admin, StateResource}, }, state::{action_states, State}, }; @@ -260,8 +261,16 @@ impl Resolve for State { let query = if user.admin { None } else { + let ids = get_resource_ids_for_non_admin( + &user.id, + ResourceTargetVariant::Deployment, + ) + .await? + .into_iter() + .flat_map(|id| ObjectId::from_str(&id)) + .collect::>(); let query = doc! { - format!("permissions.{}", user.id): { "$in": ["read", "execute", "update"] } + "_id": { "$in": ids } }; Some(query) }; diff --git a/bin/core/src/api/read/procedure.rs b/bin/core/src/api/read/procedure.rs index d7db27a66..39c60ff65 100644 --- a/bin/core/src/api/read/procedure.rs +++ b/bin/core/src/api/read/procedure.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use anyhow::Context; use async_trait::async_trait; use monitor_client::{ @@ -10,15 +12,17 @@ use monitor_client::{ }, entities::{ permission::PermissionLevel, procedure::Procedure, - resource::AddFilters, user::User, + resource::AddFilters, update::ResourceTargetVariant, user::User, }, }; -use mungos::mongodb::bson::{doc, Document}; +use mungos::mongodb::bson::{doc, oid::ObjectId, Document}; use resolver_api::Resolve; use crate::{ db::db_client, - helpers::resource::StateResource, + helpers::resource::{ + get_resource_ids_for_non_admin, StateResource, + }, state::{action_states, State}, }; @@ -81,8 +85,16 @@ impl Resolve for State { let query = if user.admin { None } else { + let ids = get_resource_ids_for_non_admin( + &user.id, + ResourceTargetVariant::Procedure, + ) + .await? + .into_iter() + .flat_map(|id| ObjectId::from_str(&id)) + .collect::>(); let query = doc! { - format!("permissions.{}", user.id): { "$in": ["read", "execute", "update"] } + "_id": { "$in": ids } }; Some(query) }; diff --git a/bin/core/src/api/read/repo.rs b/bin/core/src/api/read/repo.rs index 0a293f8de..69fd6ea66 100644 --- a/bin/core/src/api/read/repo.rs +++ b/bin/core/src/api/read/repo.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use anyhow::Context; use async_trait::async_trait; use monitor_client::{ @@ -6,15 +8,18 @@ use monitor_client::{ permission::PermissionLevel, repo::{Repo, RepoActionState, RepoListItem}, resource::AddFilters, + update::ResourceTargetVariant, user::User, }, }; -use mungos::mongodb::bson::{doc, Document}; +use mungos::mongodb::bson::{doc, oid::ObjectId, Document}; use resolver_api::Resolve; use crate::{ db::db_client, - helpers::resource::StateResource, + helpers::resource::{ + get_resource_ids_for_non_admin, StateResource, + }, state::{action_states, State}, }; @@ -81,8 +86,16 @@ impl Resolve for State { let query = if user.admin { None } else { + let ids = get_resource_ids_for_non_admin( + &user.id, + ResourceTargetVariant::Alerter, + ) + .await? + .into_iter() + .flat_map(|id| ObjectId::from_str(&id)) + .collect::>(); let query = doc! { - format!("permissions.{}", user.id): { "$in": ["read", "execute", "update"] } + "_id": { "$in": ids } }; Some(query) }; diff --git a/bin/core/src/api/write/alerter.rs b/bin/core/src/api/write/alerter.rs index 08206d6b9..dea83ff79 100644 --- a/bin/core/src/api/write/alerter.rs +++ b/bin/core/src/api/write/alerter.rs @@ -22,7 +22,8 @@ use crate::{ db::db_client, helpers::{ add_update, create_permission, make_update, - remove_from_recently_viewed, resource::StateResource, + remove_from_recently_viewed, + resource::{delete_all_permissions_on_resource, StateResource}, }, state::State, }; @@ -171,6 +172,8 @@ impl Resolve for State { .await .context("failed to delete alerter from database")?; + delete_all_permissions_on_resource(&alerter).await; + update.push_simple_log( "delete alerter", format!("deleted alerter {}", alerter.name), diff --git a/bin/core/src/api/write/build.rs b/bin/core/src/api/write/build.rs index 7b0392441..07cdd75e4 100644 --- a/bin/core/src/api/write/build.rs +++ b/bin/core/src/api/write/build.rs @@ -25,7 +25,8 @@ use crate::{ db::db_client, helpers::{ add_update, create_permission, empty_or_only_spaces, make_update, - remove_from_recently_viewed, resource::StateResource, + remove_from_recently_viewed, + resource::{delete_all_permissions_on_resource, StateResource}, update_update, }, state::{action_states, State}, @@ -189,6 +190,8 @@ impl Resolve for State { .await .context("failed to delete build from database"); + delete_all_permissions_on_resource(&build).await; + let log = match res { Ok(_) => Log::simple( "delete build", diff --git a/bin/core/src/api/write/builder.rs b/bin/core/src/api/write/builder.rs index 76d2df09c..06f7f0377 100644 --- a/bin/core/src/api/write/builder.rs +++ b/bin/core/src/api/write/builder.rs @@ -20,8 +20,9 @@ use resolver_api::Resolve; use crate::{ db::db_client, helpers::{ - add_update, create_permission, remove_from_recently_viewed, - resource::StateResource, + add_update, create_permission, make_update, + remove_from_recently_viewed, + resource::{delete_all_permissions_on_resource, StateResource}, }, state::State, }; @@ -162,8 +163,6 @@ impl Resolve for State { ) .await?; - let start_ts = monitor_timestamp(); - db_client() .await .builds @@ -180,17 +179,15 @@ impl Resolve for State { .await .context("failed to delete builder from database")?; - let mut update = Update { - target: (&builder).into(), - operation: Operation::DeleteBuilder, - start_ts, - operator: user.id.clone(), - logs: vec![Log::simple( - "delete builder", - format!("deleted builder {}", builder.name), - )], - ..Default::default() - }; + delete_all_permissions_on_resource(&builder).await; + + let mut update = + make_update(&builder, Operation::DeleteBuilder, &user); + + update.push_simple_log( + "delete builder", + format!("deleted builder {}", builder.name), + ); update.finalize(); add_update(update).await?; diff --git a/bin/core/src/api/write/deployment.rs b/bin/core/src/api/write/deployment.rs index 77bf5551c..40f3ceec7 100644 --- a/bin/core/src/api/write/deployment.rs +++ b/bin/core/src/api/write/deployment.rs @@ -27,7 +27,8 @@ use crate::{ helpers::{ add_update, create_permission, empty_or_only_spaces, get_deployment_state, make_update, periphery_client, - remove_from_recently_viewed, resource::StateResource, + remove_from_recently_viewed, + resource::{delete_all_permissions_on_resource, StateResource}, update_update, }, state::{action_states, State}, @@ -198,21 +199,13 @@ impl Resolve for State { .await?; let inner = || async move { - let start_ts = monitor_timestamp(); - let state = get_deployment_state(&deployment) .await .context("failed to get container state")?; - let mut update = Update { - target: ResourceTarget::Deployment(deployment.id.clone()), - operation: Operation::DeleteDeployment, - start_ts, - operator: user.id.clone(), - success: true, - status: UpdateStatus::InProgress, - ..Default::default() - }; + let mut update = + make_update(&deployment, Operation::DeleteDeployment, &user); + update.in_progress(); update.id = add_update(update.clone()).await?; @@ -279,6 +272,8 @@ impl Resolve for State { ), }; + delete_all_permissions_on_resource(&deployment).await; + update.logs.push(log); update.end_ts = Some(monitor_timestamp()); update.status = UpdateStatus::Complete; diff --git a/bin/core/src/api/write/permissions.rs b/bin/core/src/api/write/permissions.rs index 81ccc31c2..fdb1a5480 100644 --- a/bin/core/src/api/write/permissions.rs +++ b/bin/core/src/api/write/permissions.rs @@ -5,21 +5,23 @@ use monitor_client::{ UpdateUserPermissions, UpdateUserPermissionsOnTarget, }, entities::{ - monitor_timestamp, - update::{Log, ResourceTarget, Update, UpdateStatus}, + update::{ResourceTarget, Update}, user::User, Operation, }, }; use mungos::{ by_id::{find_one_by_id, update_one_by_id}, - mongodb::bson::{doc, Document}, + mongodb::{ + bson::{doc, Document}, + options::UpdateOptions, + }, }; use resolver_api::Resolve; use crate::{ db::db_client, - helpers::{add_update, get_user}, + helpers::{add_update, get_user, make_update}, state::State, }; @@ -35,7 +37,6 @@ impl Resolve for State { }: UpdateUserPermissions, admin: User, ) -> anyhow::Result { - let start_ts = monitor_timestamp(); if !admin.admin { return Err(anyhow!("this method is admin only")); } @@ -66,25 +67,18 @@ impl Resolve for State { None, ) .await?; - let end_ts = monitor_timestamp(); - let mut update = Update { - target: ResourceTarget::System("system".to_string()), - operation: Operation::UpdateUserPermissions, - logs: vec![Log::simple( - "modify user enabled", - format!( - "update permissions for {} ({})\nenabled: {enabled:?}\ncreate servers: {create_servers:?}\ncreate builds: {create_builds:?}", - user.username, - user.id, - ), - )], - start_ts, - end_ts: end_ts.into(), - status: UpdateStatus::Complete, - success: true, - operator: admin.id.clone(), - ..Default::default() - }; + + let mut update = make_update( + ResourceTarget::System("system".to_string()), + Operation::UpdateUserPermissions, + &admin, + ); + update.push_simple_log("modify user enabled", format!( + "update permissions for {} ({})\nenabled: {enabled:?}\ncreate servers: {create_servers:?}\ncreate builds: {create_builds:?}", + user.username, + user.id, + )); + update.finalize(); update.id = add_update(update.clone()).await?; Ok(update) } @@ -101,7 +95,6 @@ impl Resolve for State { }: UpdateUserPermissionsOnTarget, admin: User, ) -> anyhow::Result { - let start_ts = monitor_timestamp(); if !admin.admin { return Err(anyhow!("this method is admin only")); } @@ -125,23 +118,19 @@ impl Resolve for State { "level": permission.as_ref(), } }, - None + UpdateOptions::builder().upsert(true).build() ).await?; let log_text = format!( "user {} given {} permissions on {target:?}", user.username, permission, ); - let mut update = Update { - operation: Operation::UpdateUserPermissionsOnTarget, - start_ts, - success: true, - operator: admin.id.clone(), - status: UpdateStatus::Complete, - target: target.clone(), - logs: vec![Log::simple("modify permissions", log_text)], - end_ts: monitor_timestamp().into(), - ..Default::default() - }; + let mut update = make_update( + target, + Operation::UpdateUserPermissionsOnTarget, + &admin, + ); + update.push_simple_log("modify permissions", log_text); + update.finalize(); update.id = add_update(update.clone()).await?; Ok(update) } diff --git a/bin/core/src/api/write/procedure.rs b/bin/core/src/api/write/procedure.rs index 02e321966..5c021c081 100644 --- a/bin/core/src/api/write/procedure.rs +++ b/bin/core/src/api/write/procedure.rs @@ -18,7 +18,8 @@ use crate::{ db::db_client, helpers::{ add_update, create_permission, make_update, - remove_from_recently_viewed, resource::StateResource, + remove_from_recently_viewed, + resource::{delete_all_permissions_on_resource, StateResource}, update_update, }, state::{action_states, State}, @@ -224,6 +225,8 @@ impl Resolve for State { .await .context("failed to delete build from database"); + delete_all_permissions_on_resource(&procedure).await; + let log = match res { Ok(_) => Log::simple( "delete procedure", diff --git a/bin/core/src/api/write/repo.rs b/bin/core/src/api/write/repo.rs index dbf054c66..2d965f0c4 100644 --- a/bin/core/src/api/write/repo.rs +++ b/bin/core/src/api/write/repo.rs @@ -8,7 +8,7 @@ use monitor_client::{ repo::Repo, server::Server, to_monitor_name, - update::{Log, ResourceTarget, Update, UpdateStatus}, + update::{Log, ResourceTarget, Update}, user::User, Operation, }, @@ -24,7 +24,8 @@ use crate::{ db::db_client, helpers::{ add_update, create_permission, make_update, periphery_client, - remove_from_recently_viewed, resource::StateResource, + remove_from_recently_viewed, + resource::{delete_all_permissions_on_resource, StateResource}, update_update, }, state::{action_states, State}, @@ -163,25 +164,13 @@ impl Resolve for State { .to_string(); let repo: Repo = self.get_resource(&repo_id).await?; create_permission(&user, &repo, PermissionLevel::Update).await; - let update = Update { - target: ResourceTarget::Repo(repo_id), - operation: Operation::CreateRepo, - start_ts, - end_ts: Some(monitor_timestamp()), - operator: user.id.clone(), - success: true, - logs: vec![ - Log::simple( - "create repo", - format!( - "created repo\nid: {}\nname: {}", - repo.id, repo.name - ), - ), - Log::simple("config", format!("{:#?}", repo.config)), - ], - ..Default::default() - }; + let mut update = make_update(&repo, Operation::CreateRepo, &user); + update.push_simple_log( + "create repo", + format!("created repo\nid: {}\nname: {}", repo.id, repo.name), + ); + update.push_simple_log("config", format!("{:#?}", repo.config)); + update.finalize(); add_update(update).await?; @@ -214,15 +203,9 @@ impl Resolve for State { }; let inner = || async move { - let mut update = Update { - operation: Operation::DeleteRepo, - target: ResourceTarget::Repo(repo.id.clone()), - start_ts: monitor_timestamp(), - status: UpdateStatus::InProgress, - operator: user.id.clone(), - success: true, - ..Default::default() - }; + let mut update = + make_update(&repo, Operation::DeleteRepo, &user); + update.in_progress(); update.id = add_update(update.clone()).await?; let res = @@ -230,6 +213,8 @@ impl Resolve for State { .await .context("failed to delete repo from database"); + delete_all_permissions_on_resource(&repo).await; + let log = match res { Ok(_) => Log::simple( "delete repo", diff --git a/bin/core/src/api/write/server.rs b/bin/core/src/api/write/server.rs index d2ebef590..584e2bf47 100644 --- a/bin/core/src/api/write/server.rs +++ b/bin/core/src/api/write/server.rs @@ -21,9 +21,12 @@ use resolver_api::Resolve; use crate::{ db::db_client, helpers::{ - add_update, cache::server_status_cache, create_permission, - make_update, periphery_client, remove_from_recently_viewed, - resource::StateResource, update_update, + add_update, + cache::server_status_cache, + create_permission, make_update, periphery_client, + remove_from_recently_viewed, + resource::{delete_all_permissions_on_resource, StateResource}, + update_update, }, monitor::update_cache_for_server, state::{action_states, State}, @@ -110,8 +113,6 @@ impl Resolve for State { ) .await?; - let start_ts = monitor_timestamp(); - db_client() .await .builds @@ -149,17 +150,14 @@ impl Resolve for State { .await .context("failed to delete server from mongo")?; - let mut update = Update { - target: ResourceTarget::Server(id.clone()), - operation: Operation::DeleteServer, - start_ts, - operator: user.id.clone(), - logs: vec![Log::simple( - "delete server", - format!("deleted server {}", server.name), - )], - ..Default::default() - }; + delete_all_permissions_on_resource(&server).await; + + let mut update = + make_update(&server, Operation::DeleteServer, &user); + update.push_simple_log( + "delete server", + format!("deleted server {}", server.name), + ); update.finalize(); add_update(update).await?; diff --git a/bin/core/src/helpers/resource.rs b/bin/core/src/helpers/resource.rs index dff46f6c8..53c8242d0 100644 --- a/bin/core/src/helpers/resource.rs +++ b/bin/core/src/helpers/resource.rs @@ -19,7 +19,7 @@ use monitor_client::entities::{ procedure::{Procedure, ProcedureListItem, ProcedureListItemInfo}, repo::{Repo, RepoInfo, RepoListItem}, server::{Server, ServerListItem, ServerListItemInfo}, - update::ResourceTargetVariant, + update::{ResourceTarget, ResourceTargetVariant}, user::User, }; use mungos::{ @@ -96,17 +96,11 @@ pub trait StateResource< &self, user_id: &str, ) -> anyhow::Result> { - let permissions = find_collect( - &db_client().await.permissions, - doc! { "user_id": user_id, "target.type": self.resource_target_variant().as_ref() }, - None, + get_resource_ids_for_non_admin( + user_id, + self.resource_target_variant(), ) .await - .context("failed to query permissions on db")? - .into_iter() - .map(|p| p.target.extract_variant_id().1.to_string()) - .collect(); - Ok(permissions) } async fn list_resources_for_user( @@ -515,3 +509,42 @@ pub async fn get_user_permission_on_resource( .unwrap_or_default(); Ok(permission) } + +pub async fn delete_all_permissions_on_resource( + target: impl Into, +) { + let target: ResourceTarget = target.into(); + let (variant, id) = target.extract_variant_id(); + if let Err(e) = db_client() + .await + .permissions + .delete_many( + doc! { "target.type": variant.as_ref(), "target.id": &id }, + None, + ) + .await + { + warn!("failed to delete_many permissions matching target {target:?} | {e:#}"); + } +} + +pub async fn get_resource_ids_for_non_admin( + user_id: &str, + resource_type: ResourceTargetVariant, +) -> anyhow::Result> { + let permissions = find_collect( + &db_client().await.permissions, + doc! { + "user_id": user_id, + "target.type": resource_type.as_ref(), + "level": { "$in": ["Read", "Execute", "Update"] } + }, + None, + ) + .await + .context("failed to query permissions on db")? + .into_iter() + .map(|p| p.target.extract_variant_id().1.to_string()) + .collect(); + Ok(permissions) +}