responsible: { name, contact, group } says the address is a group's
inbox, not a person: no account or mail is made for it, and any
signed-in member of the group gets the suggest-a-change card. A
board's shared address was onboarded as a person before this.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
de65392917
commit
014594283a
+35
-16
@@ -461,7 +461,7 @@ fn QuestionView(
|
||||
{question
|
||||
.responsible
|
||||
.as_ref()
|
||||
.filter(|r| is_responsible(user.as_ref(), &r.contact))
|
||||
.filter(|r| is_responsible(user.as_ref(), r))
|
||||
.map(|_| view! { <SuggestEdit question_id=question.id.clone()/> })}
|
||||
{question
|
||||
.responsible
|
||||
@@ -611,12 +611,16 @@ fn Announcements(current_id: String) -> impl IntoView {
|
||||
/// bulk scrapers regex HTML for `mailto:` strings, and this keeps one
|
||||
/// out of what they see. Not a defense against a targeted scrape, just
|
||||
/// against the generic bot crawl.
|
||||
/// Whether the signed-in person is the one a page names as responsible:
|
||||
/// their Kanidm address equals the page's `responsible.contact`
|
||||
/// (`mailto:` off, case ignored).
|
||||
pub fn is_responsible(user: Option<&User>, contact: &str) -> bool {
|
||||
let contact = contact.trim().trim_start_matches("mailto:").trim().to_lowercase();
|
||||
user.and_then(|u| u.email.as_deref()).is_some_and(|e| !contact.is_empty() && e.trim().to_lowercase() == contact)
|
||||
/// Whether the signed-in person answers for a page: their Kanidm
|
||||
/// address equals the page's `responsible.contact` (`mailto:` off,
|
||||
/// case ignored), or they are in the group that holds it.
|
||||
pub fn is_responsible(user: Option<&User>, responsible: &Responsible) -> bool {
|
||||
let Some(user) = user else { return false };
|
||||
if responsible.group.as_deref().is_some_and(|g| !g.is_empty() && user.groups.iter().any(|mine| mine == g)) {
|
||||
return true;
|
||||
}
|
||||
let contact = responsible.contact.trim().trim_start_matches("mailto:").trim().to_lowercase();
|
||||
user.email.as_deref().is_some_and(|e| !contact.is_empty() && e.trim().to_lowercase() == contact)
|
||||
}
|
||||
|
||||
/// One suggestion as the person who made it sees it: what they wrote
|
||||
@@ -2557,8 +2561,8 @@ pub async fn suggest_page_edit(question_id: String, text: String) -> Result<(),
|
||||
}
|
||||
let question = state.questions.load().get(&question_id).cloned().ok_or_else(|| ServerFnError::new("no such page"))?;
|
||||
let responsible = question.responsible.clone().ok_or_else(|| ServerFnError::new("this page names no one responsible"))?;
|
||||
if !is_responsible(user.as_ref(), &responsible.contact) {
|
||||
return Err(ServerFnError::new("only the person responsible for this page may suggest changes to it here"));
|
||||
if !is_responsible(user.as_ref(), &responsible) {
|
||||
return Err(ServerFnError::new("only whoever is responsible for this page may suggest changes to it here"));
|
||||
}
|
||||
let user = user.expect("is_responsible needs a user");
|
||||
let email = user.email.clone().unwrap_or_default();
|
||||
@@ -2901,17 +2905,32 @@ mod down_the_tree_tests {
|
||||
mod responsible_tests {
|
||||
use super::is_responsible;
|
||||
use crate::auth::User;
|
||||
use crate::content::Responsible;
|
||||
|
||||
fn user(email: Option<&str>) -> User {
|
||||
User { sub: "s".into(), username: "kari".into(), display_name: "Kari".into(), groups: vec![], email: email.map(String::from) }
|
||||
fn user(email: Option<&str>, groups: &[&str]) -> User {
|
||||
User { sub: "s".into(), username: "kari".into(), display_name: "Kari".into(), groups: groups.iter().map(|g| g.to_string()).collect(), email: email.map(String::from) }
|
||||
}
|
||||
|
||||
fn named(contact: &str, group: Option<&str>) -> Responsible {
|
||||
Responsible { name: "R".into(), contact: contact.into(), group: group.map(String::from) }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_person_a_page_names_may_suggest_a_change_to_it() {
|
||||
assert!(is_responsible(Some(&user(Some("kari@vel.no"))), "mailto:Kari@Vel.no"));
|
||||
assert!(!is_responsible(Some(&user(Some("ola@vel.no"))), "kari@vel.no"));
|
||||
assert!(!is_responsible(Some(&user(None)), "kari@vel.no"));
|
||||
assert!(!is_responsible(None, "kari@vel.no"));
|
||||
assert!(!is_responsible(Some(&user(Some(""))), ""));
|
||||
assert!(is_responsible(Some(&user(Some("kari@vel.no"), &[])), &named("mailto:Kari@Vel.no", None)));
|
||||
assert!(!is_responsible(Some(&user(Some("ola@vel.no"), &[])), &named("kari@vel.no", None)));
|
||||
assert!(!is_responsible(Some(&user(None, &[])), &named("kari@vel.no", None)));
|
||||
assert!(!is_responsible(None, &named("kari@vel.no", None)));
|
||||
assert!(!is_responsible(Some(&user(Some(""), &[])), &named("", None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_page_a_group_answers_for_is_every_members() {
|
||||
let board = named("post@vel.no", Some("styret"));
|
||||
assert!(is_responsible(Some(&user(Some("ola@vel.no"), &["styret"])), &board));
|
||||
assert!(!is_responsible(Some(&user(Some("ola@vel.no"), &["trafikk"])), &board));
|
||||
// The inbox itself, signed in, still counts.
|
||||
assert!(is_responsible(Some(&user(Some("post@vel.no"), &[])), &board));
|
||||
assert!(!is_responsible(None, &board));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user