diff --git a/bin/core/src/api/auth.rs b/bin/core/src/api/auth.rs index 4cbff2ddb..ff4262f3a 100644 --- a/bin/core/src/api/auth.rs +++ b/bin/core/src/api/auth.rs @@ -19,7 +19,7 @@ use crate::{ jwt::jwt_client, }, config::core_config, - helpers::get_user, + helpers::query::get_user, state::State, }; diff --git a/bin/core/src/api/execute/build.rs b/bin/core/src/api/execute/build.rs index 097b5e758..fc1061ed7 100644 --- a/bin/core/src/api/execute/build.rs +++ b/bin/core/src/api/execute/build.rs @@ -43,9 +43,10 @@ use crate::{ config::core_config, db::db_client, helpers::{ - add_update, channel::build_cancel_channel, get_deployment_state, - make_update, periphery_client, resource::StateResource, - update_update, + channel::build_cancel_channel, + query::get_deployment_state, periphery_client, + resource::StateResource, + update::{add_update, make_update, update_update}, }, state::{action_states, State}, }; diff --git a/bin/core/src/api/execute/deployment.rs b/bin/core/src/api/execute/deployment.rs index d47e7f6b3..68f0110d6 100644 --- a/bin/core/src/api/execute/deployment.rs +++ b/bin/core/src/api/execute/deployment.rs @@ -22,8 +22,9 @@ use serror::serialize_error_pretty; use crate::{ db::db_client, helpers::{ - add_update, get_server_with_status, make_update, - periphery_client, resource::StateResource, update_update, + query::get_server_with_status, periphery_client, + resource::StateResource, + update::{add_update, make_update, update_update}, }, monitor::update_cache_for_server, state::{action_states, State}, @@ -352,7 +353,7 @@ impl Resolve for State { ) .await .context("failed to find deployments on server")?; - + let server_id = server.id.clone(); let inner = || async move { diff --git a/bin/core/src/api/execute/procedure.rs b/bin/core/src/api/execute/procedure.rs index 578c5def4..9c54873a5 100644 --- a/bin/core/src/api/execute/procedure.rs +++ b/bin/core/src/api/execute/procedure.rs @@ -12,8 +12,9 @@ use tokio::sync::Mutex; use crate::{ helpers::{ - add_update, make_update, procedure::execute_procedure, - resource::StateResource, update_update, + procedure::execute_procedure, + resource::StateResource, + update::{add_update, make_update, update_update}, }, state::State, }; diff --git a/bin/core/src/api/execute/repo.rs b/bin/core/src/api/execute/repo.rs index b12db2096..4b3cecd60 100644 --- a/bin/core/src/api/execute/repo.rs +++ b/bin/core/src/api/execute/repo.rs @@ -22,8 +22,9 @@ use serror::serialize_error_pretty; use crate::{ db::db_client, helpers::{ - add_update, periphery_client, resource::StateResource, - update_update, + periphery_client, + resource::StateResource, + update::{add_update, update_update}, }, state::{action_states, State}, }; diff --git a/bin/core/src/api/execute/server.rs b/bin/core/src/api/execute/server.rs index 40004b3e6..c55bae527 100644 --- a/bin/core/src/api/execute/server.rs +++ b/bin/core/src/api/execute/server.rs @@ -17,8 +17,9 @@ use serror::serialize_error_pretty; use crate::{ helpers::{ - add_update, make_update, periphery_client, - resource::StateResource, update_update, + periphery_client, + resource::StateResource, + update::{add_update, make_update, update_update}, }, state::{action_states, State}, }; diff --git a/bin/core/src/api/read/tag.rs b/bin/core/src/api/read/tag.rs index 2e2194134..3fb38a4a3 100644 --- a/bin/core/src/api/read/tag.rs +++ b/bin/core/src/api/read/tag.rs @@ -7,7 +7,7 @@ use monitor_client::{ use mungos::find::find_collect; use resolver_api::Resolve; -use crate::{db::db_client, helpers::get_tag, state::State}; +use crate::{db::db_client, helpers::query::get_tag, state::State}; #[async_trait] impl Resolve for State { diff --git a/bin/core/src/api/write/alerter.rs b/bin/core/src/api/write/alerter.rs index 23ee93b7f..8d2e97cda 100644 --- a/bin/core/src/api/write/alerter.rs +++ b/bin/core/src/api/write/alerter.rs @@ -23,7 +23,7 @@ use resolver_api::Resolve; use crate::{ db::db_client, helpers::{ - add_update, create_permission, make_update, + create_permission, update::{add_update, make_update}, remove_from_recently_viewed, resource::{delete_all_permissions_on_resource, StateResource}, }, diff --git a/bin/core/src/api/write/api_key.rs b/bin/core/src/api/write/api_key.rs index 869d3755c..43a1abaac 100644 --- a/bin/core/src/api/write/api_key.rs +++ b/bin/core/src/api/write/api_key.rs @@ -12,7 +12,7 @@ use mungos::{by_id::find_one_by_id, mongodb::bson::doc}; use resolver_api::Resolve; use crate::{ - auth::random_string, db::db_client, helpers::get_user, state::State, + auth::random_string, db::db_client, helpers::query::get_user, state::State, }; const SECRET_LENGTH: usize = 40; diff --git a/bin/core/src/api/write/build.rs b/bin/core/src/api/write/build.rs index 4c0c6a634..f25357798 100644 --- a/bin/core/src/api/write/build.rs +++ b/bin/core/src/api/write/build.rs @@ -24,10 +24,10 @@ use resolver_api::Resolve; use crate::{ db::db_client, helpers::{ - add_update, create_permission, empty_or_only_spaces, make_update, + create_permission, empty_or_only_spaces, remove_from_recently_viewed, resource::{delete_all_permissions_on_resource, StateResource}, - update_update, + update::{add_update, make_update, update_update}, }, state::{action_states, State}, }; diff --git a/bin/core/src/api/write/builder.rs b/bin/core/src/api/write/builder.rs index 136428919..ec521e73e 100644 --- a/bin/core/src/api/write/builder.rs +++ b/bin/core/src/api/write/builder.rs @@ -25,9 +25,9 @@ use resolver_api::Resolve; use crate::{ db::db_client, helpers::{ - add_update, create_permission, make_update, - remove_from_recently_viewed, + create_permission, remove_from_recently_viewed, resource::{delete_all_permissions_on_resource, StateResource}, + update::{add_update, make_update}, }, state::State, }; diff --git a/bin/core/src/api/write/deployment.rs b/bin/core/src/api/write/deployment.rs index cc8e32a4e..ec07d4232 100644 --- a/bin/core/src/api/write/deployment.rs +++ b/bin/core/src/api/write/deployment.rs @@ -30,11 +30,11 @@ use resolver_api::Resolve; use crate::{ db::db_client, helpers::{ - add_update, create_permission, empty_or_only_spaces, - get_deployment_state, make_update, periphery_client, + create_permission, empty_or_only_spaces, periphery_client, + query::get_deployment_state, remove_from_recently_viewed, resource::{delete_all_permissions_on_resource, StateResource}, - update_update, + update::{add_update, make_update, update_update}, }, state::{action_states, State}, }; diff --git a/bin/core/src/api/write/launch.rs b/bin/core/src/api/write/launch.rs index c23c3a3f0..7b40d1ffb 100644 --- a/bin/core/src/api/write/launch.rs +++ b/bin/core/src/api/write/launch.rs @@ -13,7 +13,7 @@ use resolver_api::Resolve; use crate::{ cloud::aws::launch_ec2_instance, - helpers::{add_update, make_update, update_update}, + helpers::update::{add_update, make_update, update_update}, state::State, }; diff --git a/bin/core/src/api/write/permissions.rs b/bin/core/src/api/write/permissions.rs index 527e47886..8688555d4 100644 --- a/bin/core/src/api/write/permissions.rs +++ b/bin/core/src/api/write/permissions.rs @@ -21,7 +21,10 @@ use resolver_api::Resolve; use crate::{ db::db_client, - helpers::{add_update, get_user, make_update}, + helpers::{ + query::get_user, + update::{add_update, make_update}, + }, state::State, }; @@ -87,7 +90,10 @@ impl Resolve for State { #[async_trait] impl Resolve for State { - #[instrument(name = "UpdateUserPermissionsOnTarget", skip(self, admin))] + #[instrument( + name = "UpdateUserPermissionsOnTarget", + skip(self, admin) + )] async fn resolve( &self, UpdateUserPermissionsOnTarget { diff --git a/bin/core/src/api/write/procedure.rs b/bin/core/src/api/write/procedure.rs index 674cfadb4..ee36292e3 100644 --- a/bin/core/src/api/write/procedure.rs +++ b/bin/core/src/api/write/procedure.rs @@ -26,10 +26,9 @@ use resolver_api::Resolve; use crate::{ db::db_client, helpers::{ - add_update, create_permission, make_update, - remove_from_recently_viewed, + create_permission, remove_from_recently_viewed, resource::{delete_all_permissions_on_resource, StateResource}, - update_update, + update::{add_update, make_update, update_update}, }, state::{action_states, State}, }; diff --git a/bin/core/src/api/write/repo.rs b/bin/core/src/api/write/repo.rs index 4897f9163..48ab1070e 100644 --- a/bin/core/src/api/write/repo.rs +++ b/bin/core/src/api/write/repo.rs @@ -26,10 +26,9 @@ use serror::serialize_error_pretty; use crate::{ db::db_client, helpers::{ - add_update, create_permission, make_update, periphery_client, - remove_from_recently_viewed, + create_permission, periphery_client, remove_from_recently_viewed, resource::{delete_all_permissions_on_resource, StateResource}, - update_update, + update::{add_update, make_update, update_update}, }, state::{action_states, State}, }; diff --git a/bin/core/src/api/write/server.rs b/bin/core/src/api/write/server.rs index 80266e571..f86605364 100644 --- a/bin/core/src/api/write/server.rs +++ b/bin/core/src/api/write/server.rs @@ -25,12 +25,10 @@ use serror::serialize_error_pretty; use crate::{ db::db_client, helpers::{ - add_update, cache::server_status_cache, - create_permission, make_update, periphery_client, - remove_from_recently_viewed, + create_permission, periphery_client, remove_from_recently_viewed, resource::{delete_all_permissions_on_resource, StateResource}, - update_update, + update::{add_update, make_update, update_update}, }, monitor::update_cache_for_server, state::{action_states, State}, diff --git a/bin/core/src/api/write/tag.rs b/bin/core/src/api/write/tag.rs index 0e20ee100..be91b1436 100644 --- a/bin/core/src/api/write/tag.rs +++ b/bin/core/src/api/write/tag.rs @@ -22,7 +22,7 @@ use resolver_api::Resolve; use crate::{ db::db_client, - helpers::{get_tag, get_tag_check_owner, resource::StateResource}, + helpers::{query::{get_tag, get_tag_check_owner}, resource::StateResource}, state::State, }; diff --git a/bin/core/src/api/write/user.rs b/bin/core/src/api/write/user.rs index 70e83e4b2..e3f864c73 100644 --- a/bin/core/src/api/write/user.rs +++ b/bin/core/src/api/write/user.rs @@ -20,7 +20,7 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::{db::db_client, helpers::get_user, state::State}; +use crate::{db::db_client, helpers::query::get_user, state::State}; const RECENTLY_VIEWED_MAX: usize = 10; diff --git a/bin/core/src/auth/mod.rs b/bin/core/src/auth/mod.rs index b16fe334b..f8b180f33 100644 --- a/bin/core/src/auth/mod.rs +++ b/bin/core/src/auth/mod.rs @@ -11,7 +11,7 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serde::Deserialize; use serror::AuthError; -use crate::{db::db_client, helpers::get_user}; +use crate::{db::db_client, helpers::query::get_user}; use self::jwt::{jwt_client, JwtClaims}; diff --git a/bin/core/src/db.rs b/bin/core/src/db.rs index 82fab1f34..91ac8352b 100644 --- a/bin/core/src/db.rs +++ b/bin/core/src/db.rs @@ -13,6 +13,7 @@ use monitor_client::entities::{ tag::Tag, update::Update, user::User, + user_group::UserGroup, }; use mungos::{ init::MongoBuilder, @@ -35,6 +36,7 @@ pub async fn db_client() -> &'static DbClient { pub struct DbClient { pub users: Collection, + pub user_groups: Collection, pub permissions: Collection, pub api_keys: Collection, pub tags: Collection, @@ -90,6 +92,7 @@ impl DbClient { let client = DbClient { users: User::collection(&db, true).await?, + user_groups: UserGroup::collection(&db, true).await?, permissions: Permission::collection(&db, true).await?, api_keys: ApiKey::collection(&db, true).await?, tags: Tag::collection(&db, true).await?, diff --git a/bin/core/src/helpers/mod.rs b/bin/core/src/helpers/mod.rs index 3b69abc36..6fb5316b2 100644 --- a/bin/core/src/helpers/mod.rs +++ b/bin/core/src/helpers/mod.rs @@ -1,32 +1,25 @@ -use std::{str::FromStr, time::Duration}; +use std::time::Duration; use anyhow::{anyhow, Context}; use monitor_client::entities::{ - deployment::{Deployment, DockerContainerState}, - monitor_timestamp, - permission::{Permission, PermissionLevel}, - server::{Server, ServerStatus}, - tag::Tag, - update::{ResourceTarget, Update, UpdateListItem}, - user::{admin_service_user, User}, - Operation, + permission::{Permission, PermissionLevel, UserTarget}, + server::Server, + update::ResourceTarget, + user::User, }; -use mungos::{ - by_id::{find_one_by_id, update_one_by_id}, - mongodb::bson::{doc, oid::ObjectId, to_document}, -}; -use periphery_client::{api, PeripheryClient}; +use mungos::mongodb::bson::doc; +use periphery_client::PeripheryClient; use rand::{thread_rng, Rng}; use crate::{config::core_config, db::db_client}; -use self::{channel::update_channel, resource::StateResource}; - pub mod alert; pub mod cache; pub mod channel; pub mod procedure; +pub mod query; pub mod resource; +pub mod update; pub fn empty_or_only_spaces(word: &str) -> bool { if word.is_empty() { @@ -44,170 +37,6 @@ pub fn random_duration(min_ms: u64, max_ms: u64) -> Duration { Duration::from_millis(thread_rng().gen_range(min_ms..max_ms)) } -pub fn make_update( - target: impl Into, - operation: Operation, - user: &User, -) -> Update { - Update { - start_ts: monitor_timestamp(), - target: target.into(), - operation, - operator: user.id.clone(), - success: true, - ..Default::default() - } -} - -#[instrument(level = "debug")] -pub async fn get_user(user_id: &str) -> anyhow::Result { - if let Some(user) = admin_service_user(user_id) { - return Ok(user); - } - find_one_by_id(&db_client().await.users, user_id) - .await - .context("failed to query mongo for user")? - .with_context(|| format!("no user found with id {user_id}")) -} - -#[instrument(level = "debug")] -pub async fn get_server_with_status( - server_id_or_name: &str, -) -> anyhow::Result<(Server, ServerStatus)> { - let server = Server::get_resource(server_id_or_name).await?; - if !server.config.enabled { - return Ok((server, ServerStatus::Disabled)); - } - let status = - match periphery_client(&server)?.request(api::GetHealth {}).await - { - Ok(_) => ServerStatus::Ok, - Err(_) => ServerStatus::NotOk, - }; - Ok((server, status)) -} - -#[instrument(level = "debug")] -pub async fn get_deployment_state( - deployment: &Deployment, -) -> anyhow::Result { - if deployment.config.server_id.is_empty() { - return Ok(DockerContainerState::NotDeployed); - } - let (server, status) = - get_server_with_status(&deployment.config.server_id).await?; - if status != ServerStatus::Ok { - return Ok(DockerContainerState::Unknown); - } - let container = periphery_client(&server)? - .request(api::container::GetContainerList {}) - .await? - .into_iter() - .find(|container| container.name == deployment.name); - - let state = match container { - Some(container) => container.state, - None => DockerContainerState::NotDeployed, - }; - - Ok(state) -} - -// TAG - -#[instrument(level = "debug")] -pub async fn get_tag(id_or_name: &str) -> anyhow::Result { - let query = match ObjectId::from_str(id_or_name) { - Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "name": id_or_name }, - }; - db_client() - .await - .tags - .find_one(query, None) - .await - .context("failed to query mongo for tag")? - .with_context(|| format!("no tag found matching {id_or_name}")) -} - -#[instrument(level = "debug")] -pub async fn get_tag_check_owner( - id_or_name: &str, - user: &User, -) -> anyhow::Result { - let tag = get_tag(id_or_name).await?; - if user.admin || tag.owner == user.id { - return Ok(tag); - } - Err(anyhow!("user must be tag owner or admin")) -} - -// UPDATE -#[instrument(level = "debug")] -async fn update_list_item( - update: Update, -) -> anyhow::Result { - let username = if User::is_service_user(&update.operator) { - update.operator.clone() - } else { - find_one_by_id(&db_client().await.users, &update.operator) - .await - .context("failed to query mongo for user")? - .with_context(|| { - format!("no user found with id {}", update.operator) - })? - .username - }; - let update = UpdateListItem { - id: update.id, - operation: update.operation, - start_ts: update.start_ts, - success: update.success, - operator: update.operator, - target: update.target, - status: update.status, - version: update.version, - username, - }; - Ok(update) -} - -#[instrument(level = "debug")] -async fn send_update(update: UpdateListItem) -> anyhow::Result<()> { - update_channel().sender.lock().await.send(update)?; - Ok(()) -} - -#[instrument(level = "debug")] -pub async fn add_update( - mut update: Update, -) -> anyhow::Result { - update.id = db_client() - .await - .updates - .insert_one(&update, None) - .await - .context("failed to insert update into db")? - .inserted_id - .as_object_id() - .context("inserted_id is not object id")? - .to_string(); - let id = update.id.clone(); - let update = update_list_item(update).await?; - let _ = send_update(update).await; - Ok(id) -} - -#[instrument(level = "debug")] -pub async fn update_update(update: Update) -> anyhow::Result<()> { - update_one_by_id(&db_client().await.updates, &update.id, mungos::update::Update::Set(to_document(&update)?), None) - .await - .context("failed to update the update on db. the update build process was deleted")?; - let update = update_list_item(update).await?; - let _ = send_update(update).await; - Ok(()) -} - #[instrument] pub async fn remove_from_recently_viewed( resource: T, @@ -275,8 +104,8 @@ pub async fn create_permission( .insert_one( Permission { id: Default::default(), - user_id: user.id.clone(), - target: target.clone(), + user_target: UserTarget::User(user.id.clone()), + resource_target: target.clone(), level, }, None, diff --git a/bin/core/src/helpers/procedure.rs b/bin/core/src/helpers/procedure.rs index 3c227ff2d..b4fcb87fd 100644 --- a/bin/core/src/helpers/procedure.rs +++ b/bin/core/src/helpers/procedure.rs @@ -16,7 +16,7 @@ use tokio::sync::Mutex; use crate::state::State; -use super::update_update; +use super::update::update_update; #[instrument] pub async fn execute_procedure( diff --git a/bin/core/src/helpers/query.rs b/bin/core/src/helpers/query.rs new file mode 100644 index 000000000..e477b3fac --- /dev/null +++ b/bin/core/src/helpers/query.rs @@ -0,0 +1,121 @@ +use std::str::FromStr; + +use anyhow::{anyhow, Context}; +use monitor_client::entities::{ + deployment::{Deployment, DockerContainerState}, + server::{Server, ServerStatus}, + tag::Tag, + user::{admin_service_user, User}, +}; +use mungos::{ + by_id::find_one_by_id, + find::find_collect, + mongodb::bson::{doc, oid::ObjectId}, +}; + +use crate::db::db_client; + +use super::resource::StateResource; + +#[instrument(level = "debug")] +pub async fn get_user(user_id: &str) -> anyhow::Result { + if let Some(user) = admin_service_user(user_id) { + return Ok(user); + } + find_one_by_id(&db_client().await.users, user_id) + .await + .context("failed to query mongo for user")? + .with_context(|| format!("no user found with id {user_id}")) +} + +#[instrument(level = "debug")] +pub async fn get_server_with_status( + server_id_or_name: &str, +) -> anyhow::Result<(Server, ServerStatus)> { + let server = Server::get_resource(server_id_or_name).await?; + if !server.config.enabled { + return Ok((server, ServerStatus::Disabled)); + } + let status = match super::periphery_client(&server)? + .request(periphery_client::api::GetHealth {}) + .await + { + Ok(_) => ServerStatus::Ok, + Err(_) => ServerStatus::NotOk, + }; + Ok((server, status)) +} + +#[instrument(level = "debug")] +pub async fn get_deployment_state( + deployment: &Deployment, +) -> anyhow::Result { + if deployment.config.server_id.is_empty() { + return Ok(DockerContainerState::NotDeployed); + } + let (server, status) = + get_server_with_status(&deployment.config.server_id).await?; + if status != ServerStatus::Ok { + return Ok(DockerContainerState::Unknown); + } + let container = super::periphery_client(&server)? + .request(periphery_client::api::container::GetContainerList {}) + .await? + .into_iter() + .find(|container| container.name == deployment.name); + + let state = match container { + Some(container) => container.state, + None => DockerContainerState::NotDeployed, + }; + + Ok(state) +} + +// TAG + +#[instrument(level = "debug")] +pub async fn get_tag(id_or_name: &str) -> anyhow::Result { + let query = match ObjectId::from_str(id_or_name) { + Ok(id) => doc! { "_id": id }, + Err(_) => doc! { "name": id_or_name }, + }; + db_client() + .await + .tags + .find_one(query, None) + .await + .context("failed to query mongo for tag")? + .with_context(|| format!("no tag found matching {id_or_name}")) +} + +#[instrument(level = "debug")] +pub async fn get_tag_check_owner( + id_or_name: &str, + user: &User, +) -> anyhow::Result { + let tag = get_tag(id_or_name).await?; + if user.admin || tag.owner == user.id { + return Ok(tag); + } + Err(anyhow!("user must be tag owner or admin")) +} + +#[instrument(level = "debug")] +pub async fn get_user_user_group_ids( + user_id: &str, +) -> anyhow::Result> { + let res = find_collect( + &db_client().await.user_groups, + doc! { + "users": user_id + }, + None, + ) + .await + .context("failed to query db for user groups")? + .into_iter() + .map(|ug| ug.id) + .collect(); + Ok(res) +} diff --git a/bin/core/src/helpers/resource.rs b/bin/core/src/helpers/resource.rs index ad94d08b4..a6337189c 100644 --- a/bin/core/src/helpers/resource.rs +++ b/bin/core/src/helpers/resource.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{collections::HashSet, str::FromStr}; use anyhow::{anyhow, Context}; use futures::future::join_all; @@ -54,7 +54,7 @@ use crate::{db::db_client, state::State}; use super::{ cache::{deployment_status_cache, server_status_cache}, - get_tag, + query::{get_tag, get_user_user_group_ids}, }; pub trait StateResource { @@ -582,21 +582,26 @@ pub async fn get_user_permission_on_resource( resource_variant: ResourceTargetVariant, resource_id: &str, ) -> anyhow::Result { - let permission = db_client() - .await - .permissions - .find_one( - doc! { - "user_id": user_id, - "target.type": resource_variant.as_ref(), - "target.id": resource_id - }, - None, - ) - .await - .context("failed to query permissions table")? - .map(|permission| permission.level) - .unwrap_or_default(); + let permission = find_collect( + &db_client().await.permissions, + doc! { + "$or": user_target_query(user_id).await?, + "target.type": resource_variant.as_ref(), + "target.id": resource_id + }, + None, + ) + .await + .context("failed to query db for permissions")? + .into_iter() + // get the max permission user has between personal / any user groups + .fold(PermissionLevel::None, |level, permission| { + if permission.level > level { + permission.level + } else { + level + } + }); Ok(permission) } @@ -628,8 +633,8 @@ pub async fn get_resource_ids_for_non_admin( let permissions = find_collect( &db_client().await.permissions, doc! { - "user_id": user_id, - "target.type": resource_type.as_ref(), + "$or": user_target_query(user_id).await?, + "resource_target.type": resource_type.as_ref(), "level": { "$in": ["Read", "Execute", "Update"] } }, None, @@ -637,9 +642,29 @@ pub async fn get_resource_ids_for_non_admin( .await .context("failed to query permissions on db")? .into_iter() - .map(|p| p.target.extract_variant_id().1.to_string()) - .collect(); - Ok(permissions) + .map(|p| p.resource_target.extract_variant_id().1.to_string()) + // collect into hashset first to remove any duplicates + .collect::>(); + Ok(permissions.into_iter().collect()) +} + +#[instrument(level = "debug")] +async fn user_target_query( + user_id: &str, +) -> anyhow::Result> { + let mut user_target_query = vec![ + doc! { "user_target.type": "User", "user_target.id": user_id }, + ]; + let user_groups = get_user_user_group_ids(user_id) + .await? + .into_iter() + .map(|ug_id| { + doc! { + "user_target.type": "UserGroup", "user_target.id": ug_id, + } + }); + user_target_query.extend(user_groups); + Ok(user_target_query) } #[instrument(level = "debug")] diff --git a/bin/core/src/helpers/update.rs b/bin/core/src/helpers/update.rs new file mode 100644 index 000000000..6f084ea9a --- /dev/null +++ b/bin/core/src/helpers/update.rs @@ -0,0 +1,95 @@ +use anyhow::Context; +use monitor_client::entities::{ + monitor_timestamp, + update::{ResourceTarget, Update, UpdateListItem}, + user::User, + Operation, +}; +use mungos::{ + by_id::{find_one_by_id, update_one_by_id}, + mongodb::bson::to_document, +}; + +use crate::db::db_client; + +use super::channel::update_channel; + +pub fn make_update( + target: impl Into, + operation: Operation, + user: &User, +) -> Update { + Update { + start_ts: monitor_timestamp(), + target: target.into(), + operation, + operator: user.id.clone(), + success: true, + ..Default::default() + } +} + +#[instrument(level = "debug")] +async fn update_list_item( + update: Update, +) -> anyhow::Result { + let username = if User::is_service_user(&update.operator) { + update.operator.clone() + } else { + find_one_by_id(&db_client().await.users, &update.operator) + .await + .context("failed to query mongo for user")? + .with_context(|| { + format!("no user found with id {}", update.operator) + })? + .username + }; + let update = UpdateListItem { + id: update.id, + operation: update.operation, + start_ts: update.start_ts, + success: update.success, + operator: update.operator, + target: update.target, + status: update.status, + version: update.version, + username, + }; + Ok(update) +} + +#[instrument(level = "debug")] +async fn send_update(update: UpdateListItem) -> anyhow::Result<()> { + update_channel().sender.lock().await.send(update)?; + Ok(()) +} + +#[instrument(level = "debug")] +pub async fn add_update( + mut update: Update, +) -> anyhow::Result { + update.id = db_client() + .await + .updates + .insert_one(&update, None) + .await + .context("failed to insert update into db")? + .inserted_id + .as_object_id() + .context("inserted_id is not object id")? + .to_string(); + let id = update.id.clone(); + let update = update_list_item(update).await?; + let _ = send_update(update).await; + Ok(id) +} + +#[instrument(level = "debug")] +pub async fn update_update(update: Update) -> anyhow::Result<()> { + update_one_by_id(&db_client().await.updates, &update.id, mungos::update::Update::Set(to_document(&update)?), None) + .await + .context("failed to update the update on db. the update build process was deleted")?; + let update = update_list_item(update).await?; + let _ = send_update(update).await; + Ok(()) +} diff --git a/client/core/rs/src/entities/mod.rs b/client/core/rs/src/entities/mod.rs index 2743517d1..7dc92b2dd 100644 --- a/client/core/rs/src/entities/mod.rs +++ b/client/core/rs/src/entities/mod.rs @@ -19,6 +19,7 @@ pub mod server; pub mod tag; pub mod update; pub mod user; +pub mod user_group; #[typeshare(serialized_as = "number")] pub type I64 = i64; diff --git a/client/core/rs/src/entities/permission.rs b/client/core/rs/src/entities/permission.rs index 686776356..25ec7452d 100644 --- a/client/core/rs/src/entities/permission.rs +++ b/client/core/rs/src/entities/permission.rs @@ -8,8 +8,7 @@ use typeshare::typeshare; use super::{update::ResourceTarget, MongoId}; - -/// Representation of a User or UserGroups permission on a resource. +/// Representation of a User or UserGroups permission on a resource. #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize, MongoIndexed)] // To query for all permissions on a target @@ -27,14 +26,24 @@ pub struct Permission { pub id: MongoId, /// Attached user #[index] - pub user_id: String, + pub user_target: UserTarget, /// The target resource - pub target: ResourceTarget, + pub resource_target: ResourceTarget, /// The permission level #[serde(default)] pub level: PermissionLevel, } +#[typeshare] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", content = "id")] +pub enum UserTarget { + /// User Id + User(String), + /// UserGroup Id + UserGroup(String), +} + /// The levels of permission that a User or UserGroup can have on a resource. #[typeshare] #[derive( diff --git a/client/core/rs/src/entities/user.rs b/client/core/rs/src/entities/user.rs index b1b1b1e04..34bd6d030 100644 --- a/client/core/rs/src/entities/user.rs +++ b/client/core/rs/src/entities/user.rs @@ -7,9 +7,7 @@ use mungos::mongodb::bson::{ use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{MongoId, I64}; - -use super::update::ResourceTarget; +use crate::entities::{update::ResourceTarget, MongoId, I64}; #[typeshare] #[derive( @@ -20,7 +18,7 @@ use super::update::ResourceTarget; #[sparse_doc_index(doc! { "config.data.github_id": 1 })] pub struct User { /// The Mongo ID of the User. - /// This field is de/serialized from/to JSON as + /// This field is de/serialized from/to JSON as /// `{ "_id": { "$oid": "..." }, ...(rest of serialized User) }` #[serde( default, diff --git a/client/core/rs/src/entities/user_group.rs b/client/core/rs/src/entities/user_group.rs new file mode 100644 index 000000000..a6bed6f53 --- /dev/null +++ b/client/core/rs/src/entities/user_group.rs @@ -0,0 +1,35 @@ +use mongo_indexed::derive::MongoIndexed; +use mungos::mongodb::bson::{ + serde_helpers::hex_string_as_object_id, Document, +}; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use super::{MongoId, I64}; + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Default, MongoIndexed, +)] +pub struct UserGroup { + /// The Mongo ID of the UserGroup. + /// This field is de/serialized from/to JSON as + /// `{ "_id": { "$oid": "..." }, ...(rest of serialized User) }` + #[serde( + default, + rename = "_id", + skip_serializing_if = "String::is_empty", + with = "hex_string_as_object_id" + )] + pub id: MongoId, + + #[unique_index] + pub name: String, + + /// User ids + #[index] + pub users: Vec, + + #[serde(default)] + pub updated_at: I64, +}