From 5506149efc190cef14961b523db182c8dd99a937 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Sat, 5 Aug 2023 12:10:23 +0200 Subject: [PATCH] Add loop protection --- .../src/controllers/members_controller.rs | 14 ++++ geneit_backend/src/models.rs | 2 +- .../src/services/members_service.rs | 83 +++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/geneit_backend/src/controllers/members_controller.rs b/geneit_backend/src/controllers/members_controller.rs index 1f83427..dafab9f 100644 --- a/geneit_backend/src/controllers/members_controller.rs +++ b/geneit_backend/src/controllers/members_controller.rs @@ -262,6 +262,20 @@ pub async fn update(m: FamilyAndMemberInPath, req: web::Json) -> return Ok(HttpResponse::BadRequest().body(e.to_string())); } + // Check for potential loops + let members = members_service::get_all_of_family(member.family_id()).await?; + let mut members_ref = Vec::with_capacity(members.len()); + members_ref.push(&member); + for m in &members { + if m.id() != member.id() { + members_ref.push(m); + } + } + if members_service::loop_detection::detect_loop(&members_ref) { + log::warn!("Membership update rejected due to detected loop!"); + return Ok(HttpResponse::NotAcceptable().json("Loop detected!")); + } + members_service::update(&mut member).await?; Ok(HttpResponse::Accepted().finish()) diff --git a/geneit_backend/src/models.rs b/geneit_backend/src/models.rs index 7532a90..f31bc62 100644 --- a/geneit_backend/src/models.rs +++ b/geneit_backend/src/models.rs @@ -118,7 +118,7 @@ pub struct FamilyMembership { } /// Member ID holder -#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)] pub struct MemberID(pub i32); #[derive(serde::Serialize, serde::Deserialize)] diff --git a/geneit_backend/src/services/members_service.rs b/geneit_backend/src/services/members_service.rs index 63c0de8..59214eb 100644 --- a/geneit_backend/src/services/members_service.rs +++ b/geneit_backend/src/services/members_service.rs @@ -106,3 +106,86 @@ pub async fn delete_all_family(family_id: FamilyID) -> anyhow::Result<()> { } Ok(()) } + +pub mod loop_detection { + use crate::models::{Member, MemberID}; + use std::collections::HashMap; + + #[derive(Debug)] + struct LoopStack<'a> { + curr: MemberID, + prev: Option<&'a LoopStack<'a>>, + } + + impl<'a> LoopStack<'a> { + pub fn contains(&self, id: MemberID) -> bool { + if let Some(ls) = &self.prev { + if ls.contains(id) { + return true; + } + } + + self.curr == id + } + } + + fn detect_loop_control( + members: &HashMap, + curr_stack: &LoopStack, + next: Option, + ) -> bool { + match next { + None => false, + Some(id) => { + if curr_stack.contains(id) { + log::debug!("Loop detected! {:?}", curr_stack); + return true; + } + + detect_loop_recurse( + members, + &LoopStack { + curr: id, + prev: Some(curr_stack), + }, + ) + } + } + } + + /// Recurse loop detection + fn detect_loop_recurse(members: &HashMap, curr_stack: &LoopStack) -> bool { + let member = match members.get(&curr_stack.curr) { + Some(m) => m, + None => { + log::warn!("Member {:?} not found in the tree for loop detection, this should never happen!", curr_stack.curr); + return false; + } + }; + + detect_loop_control(members, curr_stack, member.mother()) + || detect_loop_control(members, curr_stack, member.father()) + } + + /// Detect loops in members hierarchy + pub fn detect_loop(members: &[&Member]) -> bool { + let mut map = HashMap::new(); + for m in members { + map.insert(m.id(), m); + } + + for m in members { + if detect_loop_recurse( + &map, + &LoopStack { + curr: m.id(), + prev: None, + }, + ) { + return true; + } + } + + false + } +}