From e9c97147390157bdc2f918bc855e2a0e06bf9082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Casta=C3=B1o=20Arteaga?= Date: Wed, 27 Nov 2024 12:21:45 +0100 Subject: [PATCH] Validate users detected in some kinds of changes (#284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sergio CastaƱo Arteaga --- clowarden-core/src/services/github/mod.rs | 52 +++++++++++++++++-- clowarden-core/src/services/github/service.rs | 9 ++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/clowarden-core/src/services/github/mod.rs b/clowarden-core/src/services/github/mod.rs index 554949d..d617e0e 100644 --- a/clowarden-core/src/services/github/mod.rs +++ b/clowarden-core/src/services/github/mod.rs @@ -1,14 +1,18 @@ //! This module contains the implementation of the GitHub service handler. -use anyhow::{Context, Result}; +use std::collections::HashSet; + +use anyhow::{format_err, Context, Result}; use as_any::Downcast; use async_trait::async_trait; +use state::Changes; use tracing::debug; use crate::{ cfg::Organization, directory::{DirectoryChange, UserName}, github::{DynGH, Source}, + multierror::MultiError, services::ChangeApplied, }; @@ -56,6 +60,45 @@ impl Handler { }); Ok(invitation_id) } + + /// Validate users found in some of the changes provided. + async fn validate_users(&self, ctx: &Ctx, changes: &Changes) -> Result<()> { + let mut merr = MultiError::new(Some("invalid github service configuration".to_string())); + + // Collect users to validate from changes + let mut users_to_validate = HashSet::new(); + for change in &changes.directory { + if let DirectoryChange::TeamMemberAdded(_, user_name) = change { + users_to_validate.insert(user_name); + } + } + for change in &changes.repositories { + if let RepositoryChange::CollaboratorAdded(_, user_name, _) = change { + users_to_validate.insert(user_name); + } + } + + // Validate users collected + for user_name in users_to_validate { + match self.svc.get_user_login(ctx, user_name).await { + Ok(valid_user_name) => { + if user_name != &valid_user_name { + merr.push(format_err!( + "user[{user_name}]: invalid username, should be {valid_user_name}", + )); + } + } + Err(err) => { + merr.push(format_err!("user[{user_name}]: error validating username: {err}")); + } + } + } + + if merr.contains_errors() { + return Err(merr.into()); + } + Ok(()) + } } #[async_trait] @@ -71,13 +114,14 @@ impl ServiceHandler for Handler { .await { Ok(base_state) => { - let changes = base_state - .diff(&head_state) + let changes = base_state.diff(&head_state); + self.validate_users(&ctx, &changes).await?; + let repositories_changes = changes .repositories .into_iter() .map(|change| Box::new(change) as DynChange) .collect(); - (changes, BaseRefConfigStatus::Valid) + (repositories_changes, BaseRefConfigStatus::Valid) } Err(_) => (vec![], BaseRefConfigStatus::Invalid), }; diff --git a/clowarden-core/src/services/github/service.rs b/clowarden-core/src/services/github/service.rs index b8aedf0..2771e6f 100644 --- a/clowarden-core/src/services/github/service.rs +++ b/clowarden-core/src/services/github/service.rs @@ -70,6 +70,9 @@ pub trait Svc { user_name: &UserName, ) -> Result; + /// Get user login. + async fn get_user_login(&self, ctx: &Ctx, user_name: &UserName) -> Result; + /// List organization admins. async fn list_org_admins(&self, ctx: &Ctx) -> Result>; @@ -392,6 +395,12 @@ impl Svc for SvcApi { Ok(client.teams().get_membership_for_user_in_org(&ctx.org, team_name, user_name).await?) } + /// [Svc::get_user_login] + async fn get_user_login(&self, ctx: &Ctx, user_name: &UserName) -> Result { + let client = self.setup_client(ctx.inst_id)?; + Ok(client.users().get_by_username_public_user(user_name).await?.login) + } + /// [Svc::list_org_admins] async fn list_org_admins(&self, ctx: &Ctx) -> Result> { #[cached(