diff --git a/bin/core/src/helpers/mod.rs b/bin/core/src/helpers/mod.rs index 84f0c0168..573eaa7fb 100644 --- a/bin/core/src/helpers/mod.rs +++ b/bin/core/src/helpers/mod.rs @@ -3,25 +3,19 @@ use std::time::Duration; use anyhow::{anyhow, Context}; use monitor_types::{ entities::{ - alerter::Alerter, - build::Build, - builder::Builder, deployment::{Deployment, DockerContainerState}, - repo::Repo, server::{Server, ServerStatus}, tag::CustomTag, update::{ResourceTarget, Update}, user::User, - Operation, PermissionLevel, + Operation, }, monitor_timestamp, - permissioned::Permissioned, }; -use mungos::{mongodb::bson::doc, AggStage::*}; use periphery_client::{requests, PeripheryClient}; use rand::{thread_rng, Rng}; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, resource::Resource, state::State}; pub mod alert; pub mod cache; @@ -60,8 +54,6 @@ pub fn make_update( } impl State { - // USER - pub async fn get_user(&self, user_id: &str) -> anyhow::Result { self.db .users @@ -70,22 +62,11 @@ impl State { .context(format!("no user exists with id {user_id}")) } - // SERVER - - pub async fn get_server(&self, server_id: &str) -> anyhow::Result { - self.db - .servers - .find_one_by_id(server_id) - .await - .context("failed to get server from db")? - .context(format!("did not find any server with id {server_id}")) - } - pub async fn get_server_with_status( &self, server_id: &str, ) -> anyhow::Result<(Server, ServerStatus)> { - let server = self.get_server(server_id).await?; + let server: Server = self.get_resource(server_id).await?; if !server.config.enabled { return Ok((server, ServerStatus::Disabled)); } @@ -100,45 +81,6 @@ impl State { Ok((server, status)) } - pub async fn get_server_check_permissions( - &self, - server_id: &str, - user: &RequestUser, - permission_level: PermissionLevel, - ) -> anyhow::Result { - let server = self.get_server(server_id).await?; - let permissions = server.get_user_permissions(&user.id); - if user.is_admin || permissions >= permission_level { - Ok(server) - } else { - Err(anyhow!( - "user does not have required permissions on this server" - )) - } - } - - pub async fn get_user_permission_on_server( - &self, - user_id: &str, - server_id: &str, - ) -> anyhow::Result { - let server = self.get_server(server_id).await?; - Ok(server.get_user_permissions(user_id)) - } - - // DEPLOYMENT - - pub async fn get_deployment(&self, deployment_id: &str) -> anyhow::Result { - self.db - .deployments - .find_one_by_id(deployment_id) - .await - .context("failed to get deployment from db")? - .context(format!( - "did not find any deployment with id {deployment_id}" - )) - } - pub async fn get_deployment_state( &self, deployment: &Deployment, @@ -167,208 +109,6 @@ impl State { Ok(state) } - pub async fn get_deployment_check_permissions( - &self, - deployment_id: &str, - user: &RequestUser, - permission_level: PermissionLevel, - ) -> anyhow::Result { - let deployment = self.get_deployment(deployment_id).await?; - let permissions = deployment.get_user_permissions(&user.id); - if user.is_admin || permissions >= permission_level { - Ok(deployment) - } else { - Err(anyhow!( - "user does not have required permissions on this deployment" - )) - } - } - - pub async fn get_user_permission_on_deployment( - &self, - user_id: &str, - deployment_id: &str, - ) -> anyhow::Result { - let deployment = self.get_deployment(deployment_id).await?; - Ok(deployment.get_user_permissions(user_id)) - } - - // BUILD - - pub async fn get_build(&self, build_id: &str) -> anyhow::Result { - self.db - .builds - .find_one_by_id(build_id) - .await - .context("failed to get build from db")? - .context(format!("did not find any build with id {build_id}")) - } - - pub async fn get_build_check_permissions( - &self, - build_id: &str, - user: &RequestUser, - permission_level: PermissionLevel, - ) -> anyhow::Result { - let build = self.get_build(build_id).await?; - let permissions = build.get_user_permissions(&user.id); - if user.is_admin || permissions >= permission_level { - Ok(build) - } else { - Err(anyhow!( - "user does not have required permissions on this build" - )) - } - } - - pub async fn get_user_permission_on_build( - &self, - user_id: &str, - build_id: &str, - ) -> anyhow::Result { - let build = self.get_build(build_id).await?; - Ok(build.get_user_permissions(user_id)) - } - - pub async fn get_build_ids_for_non_admin(&self, user_id: &str) -> anyhow::Result> { - self.db - .builds - .aggregate_collect( - [ - Match(doc! { - format!("permissions.{}", user_id): { "$in": ["update", "execute", "read"] } - }), - Project(doc! { "_id": 1 }), - ], - None, - ) - .await - .context("failed to get build ids for non admin | aggregation")? - .into_iter() - .map(|d| { - let id = d - .get("_id") - .context("no _id field")? - .as_object_id() - .context("_id not ObjectId")? - .to_string(); - anyhow::Ok(id) - }) - .collect::>>() - .context("failed to get build ids for non admin | extract id from document") - } - - // BUILDER - - pub async fn get_builder(&self, builder_id: &str) -> anyhow::Result { - self.db - .builders - .find_one_by_id(builder_id) - .await - .context("failed to get builder from db")? - .context(format!("did not find any builder with id {builder_id}")) - } - - pub async fn get_builder_check_permissions( - &self, - builder_id: &str, - user: &RequestUser, - permission_level: PermissionLevel, - ) -> anyhow::Result { - let builder = self.get_builder(builder_id).await?; - let permissions = builder.get_user_permissions(&user.id); - if user.is_admin || permissions >= permission_level { - Ok(builder) - } else { - Err(anyhow!( - "user does not have required permissions on this builder" - )) - } - } - - pub async fn get_user_permission_on_builder( - &self, - user_id: &str, - builder_id: &str, - ) -> anyhow::Result { - let builder = self.get_builder(builder_id).await?; - Ok(builder.get_user_permissions(user_id)) - } - - // REPO - - pub async fn get_repo(&self, repo_id: &str) -> anyhow::Result { - self.db - .repos - .find_one_by_id(repo_id) - .await - .context("failed to get repo from db")? - .context(format!("did not find any repo with id {repo_id}")) - } - - pub async fn get_repo_check_permissions( - &self, - repo_id: &str, - user: &RequestUser, - permission_level: PermissionLevel, - ) -> anyhow::Result { - let repo = self.get_repo(repo_id).await?; - let permissions = repo.get_user_permissions(&user.id); - if user.is_admin || permissions >= permission_level { - Ok(repo) - } else { - Err(anyhow!( - "user does not have required permissions on this repo" - )) - } - } - - pub async fn get_user_permission_on_repo( - &self, - user_id: &str, - repo_id: &str, - ) -> anyhow::Result { - let repo = self.get_repo(repo_id).await?; - Ok(repo.get_user_permissions(user_id)) - } - - // ALERTER - - pub async fn get_alerter(&self, alerter_id: &str) -> anyhow::Result { - self.db - .alerters - .find_one_by_id(alerter_id) - .await - .context("failed to get alerter from mongo")? - .context(format!("did not find any alerter with id {alerter_id}")) - } - - pub async fn get_alerter_check_permissions( - &self, - alerter_id: &str, - user: &RequestUser, - permission_level: PermissionLevel, - ) -> anyhow::Result { - let alerter = self.get_alerter(alerter_id).await?; - let permissions = alerter.get_user_permissions(&user.id); - if user.is_admin || permissions >= permission_level { - Ok(alerter) - } else { - Err(anyhow!( - "user does not have required permissions on this alerter" - )) - } - } - - pub async fn get_user_permission_on_alerter( - &self, - user_id: &str, - alerter_id: &str, - ) -> anyhow::Result { - let alerter = self.get_alerter(alerter_id).await?; - Ok(alerter.get_user_permissions(user_id)) - } - // TAG pub async fn get_tag(&self, tag_id: &str) -> anyhow::Result { diff --git a/bin/core/src/listener/github.rs b/bin/core/src/listener/github.rs index 6bbb6eafc..d7b863b98 100644 --- a/bin/core/src/listener/github.rs +++ b/bin/core/src/listener/github.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Context}; use axum::{extract::Path, http::HeaderMap, routing::post, Router}; use hex::ToHex; use hmac::{Hmac, Mac}; -use monitor_types::requests::execute; +use monitor_types::{requests::execute, entities::{build::Build, repo::Repo}}; use resolver_api::Resolve; use serde::Deserialize; use sha2::Sha256; @@ -10,7 +10,7 @@ use sha2::Sha256; use crate::{ auth::InnerRequestUser, helpers::random_duration, - state::{State, StateExtension}, + state::{State, StateExtension}, resource::Resource, }; type HmacSha256 = Hmac; @@ -72,8 +72,8 @@ impl State { ) -> anyhow::Result<()> { self.verify_gh_signature(headers, &body).await?; let request_branch = extract_branch(&body)?; - let expected_branch = self.get_build(&build_id).await?.config.branch; - if request_branch != expected_branch { + let build: Build = self.get_resource(&build_id).await?; + if request_branch != build.config.branch { return Err(anyhow!("request branch does not match expected")); } self.resolve( @@ -99,8 +99,8 @@ impl State { ) -> anyhow::Result<()> { self.verify_gh_signature(headers, &body).await?; let request_branch = extract_branch(&body)?; - let expected_branch = self.get_repo(&repo_id).await?.config.branch; - if request_branch != expected_branch { + let repo: Repo = self.get_resource(&repo_id).await?; + if request_branch != repo.config.branch { return Err(anyhow!("request branch does not match expected")); } self.resolve( @@ -126,8 +126,8 @@ impl State { ) -> anyhow::Result<()> { self.verify_gh_signature(headers, &body).await?; let request_branch = extract_branch(&body)?; - let expected_branch = self.get_repo(&repo_id).await?.config.branch; - if request_branch != expected_branch { + let repo: Repo = self.get_resource(&repo_id).await?; + if request_branch != repo.config.branch { return Err(anyhow!("request branch does not match expected")); } self.resolve( diff --git a/bin/core/src/main.rs b/bin/core/src/main.rs index 2dbc75e97..a041d0ece 100644 --- a/bin/core/src/main.rs +++ b/bin/core/src/main.rs @@ -14,6 +14,7 @@ mod monitor; mod requests; mod state; mod ws; +mod resource; async fn app() -> anyhow::Result<()> { let state = state::State::load().await?; diff --git a/bin/core/src/requests/execute/build.rs b/bin/core/src/requests/execute/build.rs index 2659b4e04..b975b1585 100644 --- a/bin/core/src/requests/execute/build.rs +++ b/bin/core/src/requests/execute/build.rs @@ -7,8 +7,9 @@ use monitor_types::{ all_logs_success, entities::{ build::{Build, BuildBuilderConfig}, - builder::{AwsBuilderConfig, BuilderConfig}, + builder::{AwsBuilderConfig, Builder, BuilderConfig}, deployment::DockerContainerState, + server::Server, update::{Log, Update}, Operation, PermissionLevel, }, @@ -27,6 +28,7 @@ use crate::{ auth::{InnerRequestUser, RequestUser}, cloud::{aws::Ec2Instance, BuildCleanupData}, helpers::make_update, + resource::Resource, state::State, }; @@ -41,8 +43,8 @@ impl Resolve for State { return Err(anyhow!("build busy")); } - let mut build = self - .get_build_check_permissions(&build_id, &user, PermissionLevel::Execute) + let mut build: Build = self + .get_resource_check_permissions(&build_id, &user, PermissionLevel::Execute) .await?; build.config.version.increment(); @@ -199,7 +201,8 @@ impl Resolve for State { CancelBuild { build_id }: CancelBuild, user: RequestUser, ) -> anyhow::Result { - self.get_build_check_permissions(&build_id, &user, PermissionLevel::Execute) + let _: Build = self + .get_resource_check_permissions(&build_id, &user, PermissionLevel::Execute) .await?; self.build_cancel.sender.lock().await.send(build_id)?; Ok(CancelBuildResponse {}) @@ -220,7 +223,7 @@ impl State { if server_id.is_empty() { return Err(anyhow!("build has not configured a builder")); } - let server = self.get_server(server_id).await?; + let server: Server = self.get_resource(server_id).await?; let periphery = self.periphery_client(&server); Ok(( periphery, @@ -233,7 +236,7 @@ impl State { if builder_id.is_empty() { return Err(anyhow!("build has not configured a builder")); } - let builder = self.get_builder(builder_id).await?; + let builder: Builder = self.get_resource(builder_id).await?; match builder.config { BuilderConfig::Aws(config) => self.get_aws_builder(build, config, update).await, } diff --git a/bin/core/src/requests/execute/deployment.rs b/bin/core/src/requests/execute/deployment.rs index fe273701e..49af9c025 100644 --- a/bin/core/src/requests/execute/deployment.rs +++ b/bin/core/src/requests/execute/deployment.rs @@ -2,7 +2,8 @@ use anyhow::anyhow; use async_trait::async_trait; use monitor_types::{ entities::{ - deployment::DeploymentImage, + build::Build, + deployment::{Deployment, DeploymentImage}, server::ServerStatus, update::{Log, ResourceTarget, Update, UpdateStatus}, Operation, PermissionLevel, Version, @@ -13,7 +14,7 @@ use monitor_types::{ use periphery_client::requests; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, resource::Resource, state::State}; #[async_trait] impl Resolve for State { @@ -30,8 +31,8 @@ impl Resolve for State { return Err(anyhow!("deployment busy")); } - let mut deployment = self - .get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Execute) + let mut deployment: Deployment = self + .get_resource_check_permissions(&deployment_id, &user, PermissionLevel::Execute) .await?; if deployment.config.server_id.is_empty() { @@ -54,7 +55,7 @@ impl Resolve for State { let version = match deployment.config.image { DeploymentImage::Build { build_id, version } => { - let build = self.get_build(&build_id).await?; + let build: Build = self.get_resource(&build_id).await?; let image_name = get_image_name(&build); let version = if version.is_none() { build.config.version @@ -136,8 +137,8 @@ impl Resolve for State { return Err(anyhow!("deployment busy")); } - let deployment = self - .get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Execute) + let deployment: Deployment = self + .get_resource_check_permissions(&deployment_id, &user, PermissionLevel::Execute) .await?; if deployment.config.server_id.is_empty() { @@ -223,8 +224,8 @@ impl Resolve for State { return Err(anyhow!("deployment busy")); } - let deployment = self - .get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Execute) + let deployment: Deployment = self + .get_resource_check_permissions(&deployment_id, &user, PermissionLevel::Execute) .await?; if deployment.config.server_id.is_empty() { @@ -314,8 +315,8 @@ impl Resolve for State { return Err(anyhow!("deployment busy")); } - let deployment = self - .get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Execute) + let deployment: Deployment = self + .get_resource_check_permissions(&deployment_id, &user, PermissionLevel::Execute) .await?; if deployment.config.server_id.is_empty() { diff --git a/bin/core/src/requests/execute/repo.rs b/bin/core/src/requests/execute/repo.rs index bd5578cb8..2071247af 100644 --- a/bin/core/src/requests/execute/repo.rs +++ b/bin/core/src/requests/execute/repo.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use monitor_types::{ entities::{ update::{Log, ResourceTarget, Update, UpdateStatus}, - Operation, PermissionLevel, + Operation, PermissionLevel, repo::Repo, server::Server, }, monitor_timestamp, optional_string, requests::execute::*, @@ -12,7 +12,7 @@ use mungos::mongodb::bson::doc; use periphery_client::requests; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, state::State, resource::Resource}; #[async_trait] impl Resolve for State { @@ -21,8 +21,8 @@ impl Resolve for State { CloneRepo { id }: CloneRepo, user: RequestUser, ) -> anyhow::Result { - let repo = self - .get_repo_check_permissions(&id, &user, PermissionLevel::Execute) + let repo: Repo = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Execute) .await?; let inner = || async move { @@ -32,7 +32,7 @@ impl Resolve for State { return Err(anyhow!("repo has no server attached")); } - let server = self.get_server(&repo.config.server_id).await?; + let server: Server = self.get_resource(&repo.config.server_id).await?; let mut update = Update { operation: Operation::CloneRepo, @@ -112,8 +112,8 @@ impl Resolve for State { PullRepo { id }: PullRepo, user: RequestUser, ) -> anyhow::Result { - let repo = self - .get_repo_check_permissions(&id, &user, PermissionLevel::Update) + let repo: Repo = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let inner = || async move { @@ -123,7 +123,7 @@ impl Resolve for State { return Err(anyhow!("repo has no server attached")); } - let server = self.get_server(&repo.config.server_id).await?; + let server: Server = self.get_resource(&repo.config.server_id).await?; let mut update = Update { operation: Operation::PullRepo, diff --git a/bin/core/src/requests/execute/server.rs b/bin/core/src/requests/execute/server.rs index 93a64bbe1..96e87d56b 100644 --- a/bin/core/src/requests/execute/server.rs +++ b/bin/core/src/requests/execute/server.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use monitor_types::{ entities::{ update::{Log, ResourceTarget, Update, UpdateStatus}, - Operation, PermissionLevel, + Operation, PermissionLevel, server::Server, }, monitor_timestamp, requests::execute::*, @@ -11,7 +11,7 @@ use monitor_types::{ use periphery_client::requests; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, state::State, resource::Resource}; #[async_trait] impl Resolve for State { @@ -24,8 +24,8 @@ impl Resolve for State { return Err(anyhow!("server busy")); } - let server = self - .get_server_check_permissions(&server_id, &user, PermissionLevel::Execute) + let server: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Execute) .await?; let inner = || async { @@ -95,8 +95,8 @@ impl Resolve for State { } let inner = || async { - let server = self - .get_server_check_permissions(&server_id, &user, PermissionLevel::Execute) + let server: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Execute) .await?; let start_ts = monitor_timestamp(); @@ -165,8 +165,8 @@ impl Resolve for State { } let inner = || async { - let server = self - .get_server_check_permissions(&server_id, &user, PermissionLevel::Execute) + let server: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Execute) .await?; let start_ts = monitor_timestamp(); diff --git a/bin/core/src/requests/read/alerter.rs b/bin/core/src/requests/read/alerter.rs new file mode 100644 index 000000000..6011b8c26 --- /dev/null +++ b/bin/core/src/requests/read/alerter.rs @@ -0,0 +1,77 @@ +use anyhow::Context; +use async_trait::async_trait; +use monitor_types::{ + entities::{alerter::Alerter, PermissionLevel}, + requests::read::*, +}; +use mungos::mongodb::bson::doc; +use resolver_api::Resolve; + +use crate::{auth::RequestUser, resource::Resource, state::State}; + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + GetAlerter { id }: GetAlerter, + user: RequestUser, + ) -> anyhow::Result { + self.get_resource_check_permissions(&id, &user, PermissionLevel::Read) + .await + } +} + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + ListAlerters { query }: ListAlerters, + user: RequestUser, + ) -> anyhow::Result> { + let mut query = query.unwrap_or_default(); + if !user.is_admin { + query.insert( + format!("permissions.{}", user.id), + doc! { "$in": ["read", "execute", "update"] }, + ); + } + + let alerters = self + .db + .alerters + .get_some(query, None) + .await + .context("failed to pull alerters from mongo")?; + + Ok(alerters) + } +} + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + GetAlertersSummary {}: GetAlertersSummary, + user: RequestUser, + ) -> anyhow::Result { + let query = if user.is_admin { + None + } else { + let query = doc! { + format!("permissions.{}", user.id): { "$in": ["read", "execute", "update"] } + }; + Some(query) + }; + let total = self + .db + .alerters + .collection + .count_documents(query, None) + .await + .context("failed to count all alerter documents")?; + let res = GetAlertersSummaryResponse { + total: total as u32, + }; + Ok(res) + } +} diff --git a/bin/core/src/requests/read/build.rs b/bin/core/src/requests/read/build.rs index debf1ef69..4748f7550 100644 --- a/bin/core/src/requests/read/build.rs +++ b/bin/core/src/requests/read/build.rs @@ -10,12 +10,12 @@ use monitor_types::{ use mungos::mongodb::bson::doc; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, resource::Resource, state::State}; #[async_trait] impl Resolve for State { async fn resolve(&self, GetBuild { id }: GetBuild, user: RequestUser) -> anyhow::Result { - self.get_build_check_permissions(&id, &user, PermissionLevel::Read) + self.get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await } } @@ -27,20 +27,7 @@ impl Resolve for State { ListBuilds { query }: ListBuilds, user: RequestUser, ) -> anyhow::Result> { - let mut query = query.unwrap_or_default(); - if !user.is_admin { - query.insert( - format!("permissions.{}", user.id), - doc! { "$in": ["read", "execute", "update"] }, - ); - } - - let builds = self - .db - .builds - .get_some(query, None) - .await - .context("failed to pull builds from mongo")?; + let builds: Vec = self.list_resources_for_user(&user, query).await?; let builds = builds .into_iter() @@ -64,7 +51,7 @@ impl Resolve for State { GetBuildActionState { id }: GetBuildActionState, user: RequestUser, ) -> anyhow::Result { - self.get_build_check_permissions(&id, &user, PermissionLevel::Read) + let _: Build = self.get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await?; let action_state = self.action_states.build.get(&id).await.unwrap_or_default(); Ok(action_state) diff --git a/bin/core/src/requests/read/builder.rs b/bin/core/src/requests/read/builder.rs index af60a4470..7c760e7be 100644 --- a/bin/core/src/requests/read/builder.rs +++ b/bin/core/src/requests/read/builder.rs @@ -7,7 +7,7 @@ use monitor_types::{ use mungos::mongodb::bson::doc; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, resource::Resource, state::State}; #[async_trait] impl Resolve for State { @@ -16,7 +16,7 @@ impl Resolve for State { GetBuilder { id }: GetBuilder, user: RequestUser, ) -> anyhow::Result { - self.get_builder_check_permissions(&id, &user, PermissionLevel::Read) + self.get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await } } @@ -28,22 +28,7 @@ impl Resolve for State { ListBuilders { query }: ListBuilders, user: RequestUser, ) -> anyhow::Result> { - let mut query = query.unwrap_or_default(); - if !user.is_admin { - query.insert( - format!("permissions.{}", user.id), - doc! { "$in": ["read", "execute", "update"] }, - ); - } - - let builders = self - .db - .builders - .get_some(query, None) - .await - .context("failed to pull builders from mongo")?; - - Ok(builders) + self.list_resources_for_user(&user, query).await } } @@ -68,7 +53,7 @@ impl Resolve for State { .collection .count_documents(query, None) .await - .context("failed to count all build documents")?; + .context("failed to count all builder documents")?; let res = GetBuildersSummaryResponse { total: total as u32, }; diff --git a/bin/core/src/requests/read/deployment.rs b/bin/core/src/requests/read/deployment.rs index 2c085a841..e5ac3361b 100644 --- a/bin/core/src/requests/read/deployment.rs +++ b/bin/core/src/requests/read/deployment.rs @@ -9,6 +9,7 @@ use monitor_types::{ Deployment, DeploymentActionState, DeploymentConfig, DeploymentImage, DockerContainerState, DockerContainerStats, }, + server::Server, update::{Log, UpdateStatus}, Operation, PermissionLevel, }, @@ -18,7 +19,7 @@ use mungos::mongodb::{bson::doc, options::FindOneOptions}; use periphery_client::requests; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, resource::Resource, state::State}; #[async_trait] impl Resolve for State { @@ -27,7 +28,7 @@ impl Resolve for State { GetDeployment { id }: GetDeployment, user: RequestUser, ) -> anyhow::Result { - self.get_deployment_check_permissions(&id, &user, PermissionLevel::Read) + self.get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await } } @@ -39,19 +40,7 @@ impl Resolve for State { ListDeployments { query }: ListDeployments, user: RequestUser, ) -> anyhow::Result> { - let mut query = query.unwrap_or_default(); - if !user.is_admin { - query.insert( - format!("permissions.{}", user.id), - doc! { "$in": ["read", "execute", "update"] }, - ); - } - let deployments = self - .db - .deployments - .get_some(query, None) - .await - .context("failed to pull deployments from mongo")?; + let deployments: Vec = self.list_resources_for_user(&user, query).await?; let deployments = deployments.into_iter().map(|deployment| async { let status = self.deployment_status_cache.get(&deployment.id).await; @@ -81,7 +70,8 @@ impl Resolve for State { GetDeploymentStatus { id }: GetDeploymentStatus, user: RequestUser, ) -> anyhow::Result { - self.get_deployment_check_permissions(&id, &user, PermissionLevel::Read) + let _: Deployment = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await?; let status = self .deployment_status_cache @@ -113,12 +103,12 @@ impl Resolve for State { config: DeploymentConfig { server_id, .. }, .. } = self - .get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Read) + .get_resource_check_permissions(&deployment_id, &user, PermissionLevel::Read) .await?; if server_id.is_empty() { return Ok(Log::default()); } - let server = self.get_server(&server_id).await?; + let server: Server = self.get_resource(&server_id).await?; self.periphery_client(&server) .request(requests::GetContainerLog { name, @@ -140,7 +130,7 @@ impl Resolve for State { config: DeploymentConfig { image, .. }, .. } = self - .get_deployment_check_permissions(&deployment_id, &user, PermissionLevel::Read) + .get_resource_check_permissions(&deployment_id, &user, PermissionLevel::Read) .await?; let version = match image { DeploymentImage::Build { .. } => { @@ -196,12 +186,12 @@ impl Resolve for State { config: DeploymentConfig { server_id, .. }, .. } = self - .get_deployment_check_permissions(&id, &user, PermissionLevel::Read) + .get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await?; if server_id.is_empty() { return Err(anyhow!("deployment has no server attached")); } - let server = self.get_server(&server_id).await?; + let server: Server = self.get_resource(&server_id).await?; self.periphery_client(&server) .request(requests::GetContainerStats { name }) .await @@ -216,7 +206,8 @@ impl Resolve for State { GetDeploymentActionState { id }: GetDeploymentActionState, user: RequestUser, ) -> anyhow::Result { - self.get_deployment_check_permissions(&id, &user, PermissionLevel::Read) + let _: Deployment = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await?; let action_state = self .action_states diff --git a/bin/core/src/requests/read/mod.rs b/bin/core/src/requests/read/mod.rs index 16aa3c471..eb6a9255f 100644 --- a/bin/core/src/requests/read/mod.rs +++ b/bin/core/src/requests/read/mod.rs @@ -25,6 +25,7 @@ mod server; mod tag; mod update; mod user; +mod alerter; #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Resolver)] @@ -67,17 +68,22 @@ enum ReadRequest { ListBuilds(ListBuilds), GetBuildActionState(GetBuildActionState), - // ==== BUILDER ==== - GetBuildersSummary(GetBuildersSummary), - GetBuilder(GetBuilder), - ListBuilders(ListBuilders), - // ==== REPO ==== GetReposSummary(GetReposSummary), GetRepo(GetRepo), ListRepos(ListRepos), GetRepoActionState(GetRepoActionState), + // ==== BUILDER ==== + GetBuildersSummary(GetBuildersSummary), + GetBuilder(GetBuilder), + ListBuilders(ListBuilders), + + // ==== ALERTER ==== + GetAlertersSummary(GetAlertersSummary), + GetAlerter(GetAlerter), + ListAlerters(ListAlerters), + // ==== TAG ==== GetTag(GetTag), ListTags(ListTags), diff --git a/bin/core/src/requests/read/repo.rs b/bin/core/src/requests/read/repo.rs index 5d06a0357..a0c6e45f9 100644 --- a/bin/core/src/requests/read/repo.rs +++ b/bin/core/src/requests/read/repo.rs @@ -10,12 +10,12 @@ use monitor_types::{ use mungos::mongodb::bson::doc; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, resource::Resource, state::State}; #[async_trait] impl Resolve for State { async fn resolve(&self, GetRepo { id }: GetRepo, user: RequestUser) -> anyhow::Result { - self.get_repo_check_permissions(&id, &user, PermissionLevel::Read) + self.get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await } } @@ -27,20 +27,7 @@ impl Resolve for State { ListRepos { query }: ListRepos, user: RequestUser, ) -> anyhow::Result> { - let mut query = query.unwrap_or_default(); - if !user.is_admin { - query.insert( - format!("permissions.{}", user.id), - doc! { "$in": ["read", "execute", "update"] }, - ); - } - - let repos = self - .db - .repos - .get_some(query, None) - .await - .context("failed to pull repos from mongo")?; + let repos: Vec = self.list_resources_for_user(&user, query).await?; let repos = repos .into_iter() @@ -63,7 +50,7 @@ impl Resolve for State { GetRepoActionState { id }: GetRepoActionState, user: RequestUser, ) -> anyhow::Result { - self.get_repo_check_permissions(&id, &user, PermissionLevel::Read) + let _: Repo = self.get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await?; let action_state = self.action_states.repo.get(&id).await.unwrap_or_default(); Ok(action_state) diff --git a/bin/core/src/requests/read/server.rs b/bin/core/src/requests/read/server.rs index 84166a254..41e947b94 100644 --- a/bin/core/src/requests/read/server.rs +++ b/bin/core/src/requests/read/server.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context}; +use anyhow::anyhow; use async_trait::async_trait; use futures::future::join_all; use monitor_types::{ @@ -12,11 +12,10 @@ use monitor_types::{ }, requests::read::*, }; -use mungos::mongodb::bson::doc; use periphery_client::requests; use resolver_api::{Resolve, ResolveToString}; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, resource::Resource, state::State}; #[async_trait] impl Resolve for State { @@ -25,7 +24,8 @@ impl Resolve for State { req: GetPeripheryVersion, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&req.server_id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&req.server_id, &user, PermissionLevel::Read) .await?; let version = self .server_status_cache @@ -40,7 +40,7 @@ impl Resolve for State { #[async_trait] impl Resolve for State { async fn resolve(&self, req: GetServer, user: RequestUser) -> anyhow::Result { - self.get_server_check_permissions(&req.id, &user, PermissionLevel::Read) + self.get_resource_check_permissions(&req.id, &user, PermissionLevel::Read) .await } } @@ -52,22 +52,9 @@ impl Resolve for State { ListServers { query }: ListServers, user: RequestUser, ) -> anyhow::Result> { - let mut query = query.unwrap_or_default(); - if !user.is_admin { - query.insert( - format!("permissions.{}", user.id), - doc! { "$in": ["read", "execute", "update"] }, - ); - } + let servers = self.list_resources_for_user(&user, query).await?; - let servers = self - .db - .servers - .get_some(query, None) - .await - .context("failed to pull servers from mongo")?; - - let servers = servers.into_iter().map(|server| async { + let servers = servers.into_iter().map(|server: Server| async { let status = self.server_status_cache.get(&server.id).await; ServerListItem { id: server.id, @@ -90,7 +77,8 @@ impl Resolve for State { GetServerStatus { id }: GetServerStatus, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await?; let status = self .server_status_cache @@ -111,7 +99,8 @@ impl Resolve for State { GetServerActionState { id }: GetServerActionState, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Read) .await?; let action_state = self.action_states.server.get(&id).await.unwrap_or_default(); Ok(action_state) @@ -125,8 +114,8 @@ impl Resolve for State { GetSystemInformation { server_id }: GetSystemInformation, user: RequestUser, ) -> anyhow::Result { - let server = self - .get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let server: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; self.periphery_client(&server) .request(requests::GetSystemInformation {}) @@ -141,7 +130,8 @@ impl ResolveToString for State { GetAllSystemStats { server_id }: GetAllSystemStats, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; let status = self .server_status_cache @@ -164,7 +154,8 @@ impl ResolveToString for State { GetBasicSystemStats { server_id }: GetBasicSystemStats, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; let status = self .server_status_cache @@ -187,7 +178,8 @@ impl ResolveToString for State { GetCpuUsage { server_id }: GetCpuUsage, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; let status = self .server_status_cache @@ -210,7 +202,8 @@ impl ResolveToString for State { GetDiskUsage { server_id }: GetDiskUsage, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; let status = self .server_status_cache @@ -233,7 +226,8 @@ impl ResolveToString for State { GetNetworkUsage { server_id }: GetNetworkUsage, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; let status = self .server_status_cache @@ -256,7 +250,8 @@ impl ResolveToString for State { GetSystemProcesses { server_id }: GetSystemProcesses, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; let status = self .server_status_cache @@ -279,7 +274,8 @@ impl ResolveToString for State { GetSystemComponents { server_id }: GetSystemComponents, user: RequestUser, ) -> anyhow::Result { - self.get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let _: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; let status = self .server_status_cache @@ -302,8 +298,8 @@ impl Resolve for State { GetDockerImages { server_id }: GetDockerImages, user: RequestUser, ) -> anyhow::Result> { - let server = self - .get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let server: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; self.periphery_client(&server) .request(requests::GetImageList {}) @@ -318,8 +314,8 @@ impl Resolve for State { GetDockerNetworks { server_id }: GetDockerNetworks, user: RequestUser, ) -> anyhow::Result> { - let server = self - .get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let server: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; self.periphery_client(&server) .request(requests::GetNetworkList {}) @@ -334,8 +330,8 @@ impl Resolve for State { GetDockerContainers { server_id }: GetDockerContainers, user: RequestUser, ) -> anyhow::Result> { - let server = self - .get_server_check_permissions(&server_id, &user, PermissionLevel::Read) + let server: Server = self + .get_resource_check_permissions(&server_id, &user, PermissionLevel::Read) .await?; self.periphery_client(&server) .request(requests::GetContainerList {}) @@ -350,20 +346,7 @@ impl Resolve for State { GetServersSummary {}: GetServersSummary, user: RequestUser, ) -> anyhow::Result { - let query = if user.is_admin { - None - } else { - let query = doc! { - format!("permissions.{}", user.id): { "$in": ["read", "execute", "update"] } - }; - Some(query) - }; - let servers = self - .db - .servers - .get_some(query, None) - .await - .context("failed to get servers from db")?; + let servers: Vec = self.list_resources_for_user(&user, None).await?; let mut res = GetServersSummaryResponse::default(); for server in servers { res.total += 1; diff --git a/bin/core/src/requests/read/update.rs b/bin/core/src/requests/read/update.rs index abd1d2530..412f5abca 100644 --- a/bin/core/src/requests/read/update.rs +++ b/bin/core/src/requests/read/update.rs @@ -1,9 +1,9 @@ use async_trait::async_trait; -use monitor_types::{entities::update::Update, requests::read::ListUpdates}; +use monitor_types::{entities::{update::Update, build::Build}, requests::read::ListUpdates}; use mungos::mongodb::{bson::doc, options::FindOptions}; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, state::State, resource::Resource}; #[async_trait] impl Resolve for State { @@ -23,7 +23,7 @@ impl Resolve for State { .await?; Ok(updates) } else { - let build_ids = self.get_build_ids_for_non_admin(&user.id).await?; + let build_ids = >::get_resource_ids_for_non_admin(self, &user.id).await?; todo!() } } diff --git a/bin/core/src/requests/write/alerter.rs b/bin/core/src/requests/write/alerter.rs index 1c22e154f..9c74cf750 100644 --- a/bin/core/src/requests/write/alerter.rs +++ b/bin/core/src/requests/write/alerter.rs @@ -8,7 +8,7 @@ use monitor_types::{ use mungos::mongodb::bson::{doc, to_bson}; use resolver_api::Resolve; -use crate::{auth::RequestUser, helpers::make_update, state::State}; +use crate::{auth::RequestUser, helpers::make_update, state::State, resource::Resource}; #[async_trait] impl Resolve for State { @@ -37,7 +37,7 @@ impl Resolve for State { .create_one(alerter) .await .context("failed to add alerter to db")?; - let alerter = self.get_alerter(&alerter_id).await?; + let alerter: Alerter = self.get_resource(&alerter_id).await?; let mut update = make_update(&alerter, Operation::CreateAlerter, &user); @@ -70,7 +70,7 @@ impl Resolve for State { description, .. } = self - .get_alerter_check_permissions(&id, &user, PermissionLevel::Update) + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let start_ts = monitor_timestamp(); let alerter = Alerter { @@ -91,7 +91,7 @@ impl Resolve for State { .create_one(alerter) .await .context("failed to add alerter to db")?; - let alerter = self.get_alerter(&alerter_id).await?; + let alerter: Alerter = self.get_resource(&alerter_id).await?; let mut update = make_update(&alerter, Operation::CreateAlerter, &user); @@ -120,8 +120,8 @@ impl Resolve for State { DeleteAlerter { id }: DeleteAlerter, user: RequestUser, ) -> anyhow::Result { - let alerter = self - .get_alerter_check_permissions(&id, &user, PermissionLevel::Update) + let alerter: Alerter = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let mut update = make_update( @@ -156,8 +156,8 @@ impl Resolve for State { UpdateAlerter { id, config }: UpdateAlerter, user: RequestUser, ) -> anyhow::Result { - let alerter = self - .get_alerter_check_permissions(&id, &user, PermissionLevel::Update) + let alerter: Alerter = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let mut update = make_update(&alerter, Operation::UpdateAlerter, &user); @@ -174,7 +174,7 @@ impl Resolve for State { ) .await?; - let alerter = self.get_alerter(&id).await?; + let alerter: Alerter = self.get_resource(&id).await?; update.finalize(); self.add_update(update).await?; diff --git a/bin/core/src/requests/write/build.rs b/bin/core/src/requests/write/build.rs index a09e25dfc..32fdafeae 100644 --- a/bin/core/src/requests/write/build.rs +++ b/bin/core/src/requests/write/build.rs @@ -4,7 +4,7 @@ use monitor_types::{ entities::{ build::{Build, BuildBuilderConfig}, update::{Log, UpdateStatus}, - Operation, PermissionLevel, + Operation, PermissionLevel, server::Server, builder::Builder, }, monitor_timestamp, requests::write::*, @@ -15,7 +15,7 @@ use resolver_api::Resolve; use crate::{ auth::RequestUser, helpers::{empty_or_only_spaces, make_update}, - state::State, + state::State, resource::Resource, }; #[async_trait] @@ -28,7 +28,7 @@ impl Resolve for State { if let Some(builder) = &config.builder { match builder { BuildBuilderConfig::Server { server_id } => { - self.get_server_check_permissions( + let _: Server = self.get_resource_check_permissions( server_id, &user, PermissionLevel::Update, @@ -37,7 +37,7 @@ impl Resolve for State { .context("cannot create build on this server. user must have update permissions on the server.")?; } BuildBuilderConfig::Builder { builder_id } => { - self.get_builder_check_permissions( + let _: Builder = self.get_resource_check_permissions( builder_id, &user, PermissionLevel::Read, @@ -66,7 +66,7 @@ impl Resolve for State { .create_one(build) .await .context("failed to add build to db")?; - let build = self.get_build(&build_id).await?; + let build: Build = self.get_resource(&build_id).await?; let mut update = make_update(&build, Operation::CreateBuild, &user); @@ -98,11 +98,11 @@ impl Resolve for State { tags, .. } = self - .get_build_check_permissions(&id, &user, PermissionLevel::Update) + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; match &config.builder { BuildBuilderConfig::Server { server_id } => { - self.get_server_check_permissions( + let _: Server = self.get_resource_check_permissions( server_id, &user, PermissionLevel::Update, @@ -111,7 +111,7 @@ impl Resolve for State { .context("cannot create build on this server. user must have update permissions on the server.")?; } BuildBuilderConfig::Builder { builder_id } => { - self.get_builder_check_permissions( + let _: Builder = self.get_resource_check_permissions( builder_id, &user, PermissionLevel::Read, @@ -139,7 +139,7 @@ impl Resolve for State { .create_one(build) .await .context("failed to add build to db")?; - let build = self.get_build(&build_id).await?; + let build: Build = self.get_resource(&build_id).await?; let mut update = make_update(&build, Operation::CreateBuild, &user); @@ -168,8 +168,8 @@ impl Resolve for State { return Err(anyhow!("build busy")); } - let build = self - .get_build_check_permissions(&id, &user, PermissionLevel::Update) + let build: Build = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let mut update = make_update(&build, Operation::DeleteBuild, &user); @@ -207,15 +207,15 @@ impl Resolve for State { return Err(anyhow!("build busy")); } - let build = self - .get_build_check_permissions(&id, &user, PermissionLevel::Update) + let build: Build = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let inner = || async move { if let Some(builder) = &config.builder { match builder { BuildBuilderConfig::Server { server_id } => { - self.get_server_check_permissions( + let _: Server = self.get_resource_check_permissions( server_id, &user, PermissionLevel::Update, @@ -224,7 +224,7 @@ impl Resolve for State { .context("cannot create build on this server. user must have update permissions on the server.")?; } BuildBuilderConfig::Builder { builder_id } => { - self.get_builder_check_permissions( + let _: Builder = self.get_resource_check_permissions( builder_id, &user, PermissionLevel::Read, @@ -261,7 +261,7 @@ impl Resolve for State { self.add_update(update).await?; - let build = self.get_build(&build.id).await?; + let build: Build = self.get_resource(&build.id).await?; anyhow::Ok(build) }; diff --git a/bin/core/src/requests/write/builder.rs b/bin/core/src/requests/write/builder.rs index 3214e3e9f..b17a3b481 100644 --- a/bin/core/src/requests/write/builder.rs +++ b/bin/core/src/requests/write/builder.rs @@ -12,7 +12,7 @@ use monitor_types::{ use mungos::mongodb::bson::{doc, to_bson}; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, state::State, resource::Resource}; #[async_trait] impl Resolve for State { @@ -39,7 +39,7 @@ impl Resolve for State { .create_one(builder) .await .context("failed to add builder to db")?; - let builder = self.get_builder(&builder_id).await?; + let builder: Builder = self.get_resource(&builder_id).await?; let update = Update { target: ResourceTarget::Builder(builder_id), operation: Operation::CreateBuilder, @@ -78,7 +78,7 @@ impl Resolve for State { description, .. } = self - .get_builder_check_permissions(&id, &user, PermissionLevel::Update) + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let start_ts = monitor_timestamp(); let builder = Builder { @@ -98,7 +98,7 @@ impl Resolve for State { .create_one(builder) .await .context("failed to add builder to db")?; - let builder = self.get_builder(&builder_id).await?; + let builder: Builder = self.get_resource(&builder_id).await?; let update = Update { target: ResourceTarget::Builder(builder_id), operation: Operation::CreateBuilder, @@ -132,8 +132,8 @@ impl Resolve for State { DeleteBuilder { id }: DeleteBuilder, user: RequestUser, ) -> anyhow::Result { - let builder = self - .get_builder_check_permissions(&id, &user, PermissionLevel::Update) + let builder: Builder = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let start_ts = monitor_timestamp(); @@ -178,8 +178,8 @@ impl Resolve for State { UpdateBuilder { id, config }: UpdateBuilder, user: RequestUser, ) -> anyhow::Result { - let builder = self - .get_builder_check_permissions(&id, &user, PermissionLevel::Update) + let builder: Builder = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let mut update = Update { @@ -204,7 +204,7 @@ impl Resolve for State { ) .await?; - let builder = self.get_builder(&id).await?; + let builder: Builder = self.get_resource(&id).await?; update.finalize(); self.add_update(update).await?; diff --git a/bin/core/src/requests/write/deployment.rs b/bin/core/src/requests/write/deployment.rs index f66a7729b..e80da71f4 100644 --- a/bin/core/src/requests/write/deployment.rs +++ b/bin/core/src/requests/write/deployment.rs @@ -3,7 +3,9 @@ use async_trait::async_trait; use monitor_types::{ all_logs_success, entities::{ + build::Build, deployment::{Deployment, DeploymentImage, DockerContainerState}, + server::Server, update::{Log, ResourceTarget, Update, UpdateStatus}, Operation, PermissionLevel, }, @@ -15,7 +17,7 @@ use mungos::mongodb::bson::{doc, to_bson}; use periphery_client::requests; use resolver_api::Resolve; -use crate::{auth::RequestUser, helpers::empty_or_only_spaces, state::State}; +use crate::{auth::RequestUser, helpers::empty_or_only_spaces, resource::Resource, state::State}; #[async_trait] impl Resolve for State { @@ -26,14 +28,14 @@ impl Resolve for State { ) -> anyhow::Result { if let Some(server_id) = &config.server_id { if !server_id.is_empty() { - self.get_server_check_permissions(server_id, &user, PermissionLevel::Update) + let _: Server = self.get_resource_check_permissions(server_id, &user, PermissionLevel::Update) .await .context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?; } } if let Some(DeploymentImage::Build { build_id, .. }) = &config.image { if !build_id.is_empty() { - self.get_build_check_permissions(build_id, &user, PermissionLevel::Read) + let _: Build = self.get_resource_check_permissions(build_id, &user, PermissionLevel::Read) .await .context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?; } @@ -56,7 +58,7 @@ impl Resolve for State { .create_one(&deployment) .await .context("failed to add deployment to db")?; - let deployment = self.get_deployment(&deployment_id).await?; + let deployment: Deployment = self.get_resource(&deployment_id).await?; let update = Update { target: ResourceTarget::Deployment(deployment_id), operation: Operation::CreateDeployment, @@ -96,16 +98,16 @@ impl Resolve for State { tags, .. } = self - .get_deployment_check_permissions(&id, &user, PermissionLevel::Update) + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; if !config.server_id.is_empty() { - self.get_server_check_permissions(&config.server_id, &user, PermissionLevel::Update) + let _: Server = self.get_resource_check_permissions(&config.server_id, &user, PermissionLevel::Update) .await .context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?; } if let DeploymentImage::Build { build_id, .. } = &config.image { if !build_id.is_empty() { - self.get_build_check_permissions(build_id, &user, PermissionLevel::Read) + let _: Build = self.get_resource_check_permissions(build_id, &user, PermissionLevel::Read) .await .context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?; } @@ -128,7 +130,7 @@ impl Resolve for State { .create_one(&deployment) .await .context("failed to add deployment to db")?; - let deployment = self.get_deployment(&deployment_id).await?; + let deployment: Deployment = self.get_resource(&deployment_id).await?; let update = Update { target: ResourceTarget::Deployment(deployment_id), operation: Operation::CreateDeployment, @@ -166,8 +168,8 @@ impl Resolve for State { return Err(anyhow!("deployment busy")); } - let deployment = self - .get_deployment_check_permissions(&id, &user, PermissionLevel::Update) + let deployment: Deployment = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let inner = || async move { @@ -195,7 +197,8 @@ impl Resolve for State { DockerContainerState::NotDeployed | DockerContainerState::Unknown ) { // container needs to be destroyed - let server = self.get_server(&deployment.config.server_id).await; + let server: anyhow::Result = + self.get_resource(&deployment.config.server_id).await; if let Err(e) = server { update.logs.push(Log::error( "remove container", @@ -283,20 +286,20 @@ impl Resolve for State { return Err(anyhow!("deployment busy")); } - let deployment = self - .get_deployment_check_permissions(&id, &user, PermissionLevel::Update) + let deployment: Deployment = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let inner = || async move { let start_ts = monitor_timestamp(); if let Some(server_id) = &config.server_id { - self.get_server_check_permissions(server_id, &user, PermissionLevel::Update) + let _: Server = self.get_resource_check_permissions(server_id, &user, PermissionLevel::Update) .await .context("cannot create deployment on this server. user must have update permissions on the server to perform this action.")?; } if let Some(DeploymentImage::Build { build_id, .. }) = &config.image { - self.get_build_check_permissions(build_id, &user, PermissionLevel::Read) + let _: Build = self.get_resource_check_permissions(build_id, &user, PermissionLevel::Read) .await .context("cannot create deployment with this build attached. user must have at least read permissions on the build to perform this action.")?; } @@ -346,7 +349,7 @@ impl Resolve for State { self.add_update(update).await?; - let deployment = self.get_deployment(&id).await?; + let deployment: Deployment = self.get_resource(&id).await?; anyhow::Ok(deployment) }; @@ -382,8 +385,8 @@ impl Resolve for State { return Err(anyhow!("deployment busy")); } - let deployment = self - .get_deployment_check_permissions(&id, &user, PermissionLevel::Update) + let deployment: Deployment = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let inner = || async { @@ -401,7 +404,7 @@ impl Resolve for State { } if container_state != DockerContainerState::NotDeployed { - let server = self.get_server(&deployment.config.server_id).await?; + let server: Server = self.get_resource(&deployment.config.server_id).await?; match self .periphery_client(&server) .request(requests::RenameContainer { diff --git a/bin/core/src/requests/write/repo.rs b/bin/core/src/requests/write/repo.rs index 0575e78fd..68436040b 100644 --- a/bin/core/src/requests/write/repo.rs +++ b/bin/core/src/requests/write/repo.rs @@ -4,7 +4,7 @@ use monitor_types::{ entities::{ repo::Repo, update::{Log, ResourceTarget, Update, UpdateStatus}, - Operation, PermissionLevel, + Operation, PermissionLevel, server::Server, }, monitor_timestamp, requests::{execute, write::*}, @@ -13,7 +13,7 @@ use mungos::mongodb::bson::{doc, to_bson}; use periphery_client::requests; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, state::State, resource::Resource}; #[async_trait] impl Resolve for State { @@ -24,7 +24,7 @@ impl Resolve for State { ) -> anyhow::Result { if let Some(server_id) = &config.server_id { if !server_id.is_empty() { - self.get_server_check_permissions( + let _: Server = self.get_resource_check_permissions( server_id, &user, PermissionLevel::Update, @@ -53,7 +53,7 @@ impl Resolve for State { .await .context("failed to add repo to db")?; - let repo = self.get_repo(&repo_id).await?; + let repo: Repo = self.get_resource(&repo_id).await?; let update = Update { target: ResourceTarget::Repo(repo_id), @@ -102,10 +102,10 @@ impl Resolve for State { tags, .. } = self - .get_repo_check_permissions(&id, &user, PermissionLevel::Update) + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; if !config.server_id.is_empty() { - self.get_server_check_permissions( + let _: Server = self.get_resource_check_permissions( &config.server_id, &user, PermissionLevel::Update, @@ -132,7 +132,7 @@ impl Resolve for State { .create_one(repo) .await .context("failed to add repo to db")?; - let repo = self.get_repo(&repo_id).await?; + let repo: Repo = self.get_resource(&repo_id).await?; let update = Update { target: ResourceTarget::Repo(repo_id), operation: Operation::CreateRepo, @@ -163,8 +163,8 @@ impl Resolve for State { DeleteRepo { id }: DeleteRepo, user: RequestUser, ) -> anyhow::Result { - let repo = self - .get_repo_check_permissions(&id, &user, PermissionLevel::Update) + let repo: Repo = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let inner = || async move { @@ -194,7 +194,7 @@ impl Resolve for State { update.logs.push(log); if !repo.config.server_id.is_empty() { - let server = self.get_server(&repo.config.server_id).await; + let server: anyhow::Result = self.get_resource(&repo.config.server_id).await; if let Ok(server) = server { match self .periphery_client(&server) @@ -248,8 +248,8 @@ impl Resolve for State { UpdateRepo { id, config }: UpdateRepo, user: RequestUser, ) -> anyhow::Result { - let repo = self - .get_repo_check_permissions(&id, &user, PermissionLevel::Update) + let repo: Repo = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let inner = || async move { @@ -257,7 +257,7 @@ impl Resolve for State { if let Some(server_id) = &config.server_id { if !server_id.is_empty() { - self.get_server_check_permissions( + let _: Server = self.get_resource_check_permissions( server_id, &user, PermissionLevel::Update, @@ -297,7 +297,7 @@ impl Resolve for State { if new_server_id != repo.config.server_id { if !repo.config.server_id.is_empty() { // clean up old server - let old_server = self.get_server(&repo.config.server_id).await?; + let old_server: Server = self.get_resource(&repo.config.server_id).await?; let res = self .periphery_client(&old_server) .request(requests::DeleteRepo { name: repo.name }) @@ -323,7 +323,7 @@ impl Resolve for State { } } - let repo = self.get_repo(&repo.id).await?; + let repo: Repo = self.get_resource(&repo.id).await?; anyhow::Ok(repo) }; diff --git a/bin/core/src/requests/write/server.rs b/bin/core/src/requests/write/server.rs index 7ef884f94..d8edb7238 100644 --- a/bin/core/src/requests/write/server.rs +++ b/bin/core/src/requests/write/server.rs @@ -12,7 +12,7 @@ use monitor_types::{ use mungos::mongodb::bson::{doc, to_bson}; use resolver_api::Resolve; -use crate::{auth::RequestUser, state::State}; +use crate::{auth::RequestUser, state::State, resource::Resource}; #[async_trait] impl Resolve for State { @@ -38,7 +38,7 @@ impl Resolve for State { .create_one(&server) .await .context("failed to add server to db")?; - let server = self.get_server(&server_id).await?; + let server: Server = self.get_resource(&server_id).await?; let update = Update { target: ResourceTarget::Server(server_id), operation: Operation::CreateServer, @@ -75,8 +75,8 @@ impl Resolve for State { return Err(anyhow!("server busy")); } - let server = self - .get_server_check_permissions(&id, &user, PermissionLevel::Update) + let server: Server = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; let start_ts = monitor_timestamp(); @@ -146,7 +146,7 @@ impl Resolve for State { return Err(anyhow!("server busy")); } let start_ts = monitor_timestamp(); - self.get_server_check_permissions(&id, &user, PermissionLevel::Update) + let _: Server = self.get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; self.db .servers @@ -171,7 +171,7 @@ impl Resolve for State { ..Default::default() }; - let new_server = self.get_server(&id).await?; + let new_server: Server = self.get_resource(&id).await?; self.update_cache_for_server(&new_server, 0).await; @@ -189,8 +189,8 @@ impl Resolve for State { user: RequestUser, ) -> anyhow::Result { let start_ts = monitor_timestamp(); - let server = self - .get_server_check_permissions(&id, &user, PermissionLevel::Update) + let server: Server = self + .get_resource_check_permissions(&id, &user, PermissionLevel::Update) .await?; self.db .updates diff --git a/bin/core/src/resource.rs b/bin/core/src/resource.rs new file mode 100644 index 000000000..d6892b258 --- /dev/null +++ b/bin/core/src/resource.rs @@ -0,0 +1,170 @@ +use anyhow::{anyhow, Context}; +use async_trait::async_trait; +use monitor_types::{ + entities::{ + alerter::Alerter, build::Build, builder::Builder, deployment::Deployment, repo::Repo, + server::Server, PermissionLevel, + }, + permissioned::Permissioned, +}; +use mungos::{ + mongodb::bson::{doc, Document}, + AggStage::*, + Collection, Indexed, +}; + +use crate::{auth::RequestUser, state::State}; + +#[async_trait] +pub trait Resource { + fn name() -> &'static str; + fn coll(&self) -> &Collection; + + async fn get_resource(&self, id: &str) -> anyhow::Result { + self.coll() + .find_one_by_id(id) + .await + .context(format!("failed to get {} from db", Self::name()))? + .context(format!("did not find any {} with id {id}", Self::name())) + } + + async fn get_resource_check_permissions( + &self, + id: &str, + user: &RequestUser, + permission_level: PermissionLevel, + ) -> anyhow::Result { + let resource = self.get_resource(id).await?; + let permissions = resource.get_user_permissions(&user.id); + if user.is_admin || permissions >= permission_level { + Ok(resource) + } else { + Err(anyhow!( + "user does not have required permissions on this {}", + Self::name() + )) + } + } + + async fn get_user_permission_on_resource( + &self, + user_id: &str, + resource_id: &str, + ) -> anyhow::Result { + let resource = self.get_resource(resource_id).await?; + Ok(resource.get_user_permissions(user_id)) + } + + async fn get_resource_ids_for_non_admin(&self, user_id: &str) -> anyhow::Result> { + self.coll() + .aggregate_collect( + [ + Match(doc! { + format!("permissions.{}", user_id): { "$in": ["update", "execute", "read"] } + }), + Project(doc! { "_id": 1 }), + ], + None, + ) + .await + .context(format!( + "failed to get {} ids for non admin | aggregation", + Self::name() + ))? + .into_iter() + .map(|d| { + let id = d + .get("_id") + .context("no _id field")? + .as_object_id() + .context("_id not ObjectId")? + .to_string(); + anyhow::Ok(id) + }) + .collect::>>() + .context(format!( + "failed to get {} ids for non admin | extract id from document", + Self::name() + )) + } + + async fn list_resources_for_user( + &self, + user: &RequestUser, + query: Option, + ) -> anyhow::Result> { + let mut query = query.unwrap_or_default(); + if !user.is_admin { + query.insert( + format!("permissions.{}", user.id), + doc! { "$in": ["read", "execute", "update"] }, + ); + } + self.coll() + .get_some(query, None) + .await + .context(format!("failed to pull {}s from mongo", Self::name())) + } +} + + + +impl Resource for State { + fn name() -> &'static str { + "server" + } + + fn coll(&self) -> &Collection { + &self.db.servers + } +} + +impl Resource for State { + fn name() -> &'static str { + "deployment" + } + + fn coll(&self) -> &Collection { + &self.db.deployments + } +} + +impl Resource for State { + fn name() -> &'static str { + "build" + } + + fn coll(&self) -> &Collection { + &self.db.builds + } +} + +impl Resource for State { + fn name() -> &'static str { + "builder" + } + + fn coll(&self) -> &Collection { + &self.db.builders + } +} + +impl Resource for State { + fn name() -> &'static str { + "repo" + } + + fn coll(&self) -> &Collection { + &self.db.repos + } +} + +impl Resource for State { + fn name() -> &'static str { + "alerter" + } + + fn coll(&self) -> &Collection { + &self.db.alerters + } +} diff --git a/bin/core/src/ws.rs b/bin/core/src/ws.rs index c5012dfe7..fd444d9d8 100644 --- a/bin/core/src/ws.rs +++ b/bin/core/src/ws.rs @@ -9,10 +9,19 @@ use axum::{ Router, }; use futures::{SinkExt, StreamExt}; -use monitor_types::entities::{ - update::{ResourceTarget, ResourceTargetVariant}, - user::User, - PermissionLevel, +use monitor_types::{ + entities::{ + alerter::Alerter, + build::Build, + builder::Builder, + deployment::Deployment, + repo::Repo, + server::Server, + update::{ResourceTarget, ResourceTargetVariant}, + user::User, + PermissionLevel, + }, + permissioned::Permissioned, }; use serde_json::json; use tokio::select; @@ -20,6 +29,7 @@ use tokio_util::sync::CancellationToken; use crate::{ auth::RequestUser, + resource::Resource, state::{State, StateExtension}, }; @@ -143,36 +153,46 @@ impl State { } let (permissions, target) = match update_target { ResourceTarget::Server(server_id) => { - let permissions = self - .get_user_permission_on_server(user_id, server_id) - .await?; - (permissions, ResourceTargetVariant::Server) + let resource: Server = self.get_resource(server_id).await?; + ( + resource.get_user_permissions(user_id), + ResourceTargetVariant::Server, + ) } ResourceTarget::Deployment(deployment_id) => { - let permissions = self - .get_user_permission_on_deployment(user_id, deployment_id) - .await?; - (permissions, ResourceTargetVariant::Deployment) + let resource: Deployment = self.get_resource(deployment_id).await?; + ( + resource.get_user_permissions(user_id), + ResourceTargetVariant::Deployment, + ) } ResourceTarget::Build(build_id) => { - let permissions = self.get_user_permission_on_build(user_id, build_id).await?; - (permissions, ResourceTargetVariant::Build) + let resource: Build = self.get_resource(build_id).await?; + ( + resource.get_user_permissions(user_id), + ResourceTargetVariant::Build, + ) } ResourceTarget::Builder(builder_id) => { - let permissions = self - .get_user_permission_on_builder(user_id, builder_id) - .await?; - (permissions, ResourceTargetVariant::Builder) + let resource: Builder = self.get_resource(builder_id).await?; + ( + resource.get_user_permissions(user_id), + ResourceTargetVariant::Builder, + ) } ResourceTarget::Repo(repo_id) => { - let permissions = self.get_user_permission_on_repo(user_id, repo_id).await?; - (permissions, ResourceTargetVariant::Repo) + let resource: Repo = self.get_resource(repo_id).await?; + ( + resource.get_user_permissions(user_id), + ResourceTargetVariant::Repo, + ) } ResourceTarget::Alerter(alerter_id) => { - let permissions = self - .get_user_permission_on_alerter(user_id, alerter_id) - .await?; - (permissions, ResourceTargetVariant::Alerter) + let resource: Alerter = self.get_resource(alerter_id).await?; + ( + resource.get_user_permissions(user_id), + ResourceTargetVariant::Alerter, + ) } ResourceTarget::System => { return Err(anyhow!("user not admin, can't recieve system updates")) diff --git a/lib/types/src/requests/read/alerter.rs b/lib/types/src/requests/read/alerter.rs index e69de29bb..5ce706a00 100644 --- a/lib/types/src/requests/read/alerter.rs +++ b/lib/types/src/requests/read/alerter.rs @@ -0,0 +1,36 @@ +use resolver_api::derive::Request; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::{entities::alerter::Alerter, MongoDocument}; + +// + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Alerter)] +pub struct GetAlerter { + pub id: String, +} + +// + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Vec)] +pub struct ListAlerters { + pub query: Option, +} + +// + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(GetAlertersSummaryResponse)] +pub struct GetAlertersSummary {} + +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetAlertersSummaryResponse { + pub total: u32, +}