Add loop protection
This commit is contained in:
		@@ -262,6 +262,20 @@ pub async fn update(m: FamilyAndMemberInPath, req: web::Json<MemberRequest>) ->
 | 
				
			|||||||
        return Ok(HttpResponse::BadRequest().body(e.to_string()));
 | 
					        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?;
 | 
					    members_service::update(&mut member).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(HttpResponse::Accepted().finish())
 | 
					    Ok(HttpResponse::Accepted().finish())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -118,7 +118,7 @@ pub struct FamilyMembership {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Member ID holder
 | 
					/// 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);
 | 
					pub struct MemberID(pub i32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,3 +106,86 @@ pub async fn delete_all_family(family_id: FamilyID) -> anyhow::Result<()> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    Ok(())
 | 
					    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<MemberID, &&Member>,
 | 
				
			||||||
 | 
					        curr_stack: &LoopStack,
 | 
				
			||||||
 | 
					        next: Option<MemberID>,
 | 
				
			||||||
 | 
					    ) -> 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<MemberID, &&Member>, 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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user