From ef53b1f01acbda586424805d604f96ea00f7c981 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Tue, 27 Nov 2018 19:47:05 +0100 Subject: validate list emails --- src/data.rs | 23 +++++++++++++++++------ src/main.rs | 9 +++++++++ src/schema.rs | 12 ++++++++++++ src/validate.rs | 22 +++++++++++++++++++++- 4 files changed, 59 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/data.rs b/src/data.rs index ae8a0eb..22af7a2 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,13 +1,15 @@ -use crate::schema::{List, Person, Team}; +use crate::schema::{List, Person, Team, Config}; use failure::{Error, ResultExt}; use serde::Deserialize; use std::collections::HashMap; use std::ffi::OsStr; +use std::path::Path; #[derive(Debug)] pub(crate) struct Data { people: HashMap, teams: HashMap, + config: Config, } impl Data { @@ -15,6 +17,7 @@ impl Data { let mut data = Data { people: HashMap::new(), teams: HashMap::new(), + config: load_file(&Path::new("config.toml"))?, }; data.load_dir("people", |this, person: Person| { @@ -37,17 +40,17 @@ impl Data { let path = entry?.path(); if path.is_file() && path.extension() == Some(OsStr::new("toml")) { - let content = std::fs::read(&path) - .with_context(|_| format!("failed to read {}", path.display()))?; - let parsed: T = toml::from_slice(&content) - .with_context(|_| format!("failed to parse {}", path.display()))?; - f(self, parsed); + f(self, load_file(&path)?); } } Ok(()) } + pub(crate) fn config(&self) -> &Config { + &self.config + } + pub(crate) fn lists(&self) -> Result, Error> { let mut lists = HashMap::new(); for team in self.teams.values() { @@ -79,3 +82,11 @@ impl Data { self.people.values() } } + +fn load_file Deserialize<'de>>(path: &Path) -> Result { + let content = std::fs::read(&path) + .with_context(|_| format!("failed to read {}", path.display()))?; + let parsed = toml::from_slice(&content) + .with_context(|_| format!("failed to parse {}", path.display()))?; + Ok(parsed) +} diff --git a/src/main.rs b/src/main.rs index 540c038..7ae31a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,8 @@ enum Cli { DumpTeam { name: String }, #[structopt(name = "dump-list", help = "print all the emails in a list")] DumpList { name: String }, + #[structopt(name = "foo")] + Tmp, } fn main() { @@ -63,6 +65,13 @@ fn run() -> Result<(), Error> { println!("{}", email); } } + Cli::Tmp => { + for list in data.lists()?.values() { + for member in list.emails() { + println!("{} {} {}", list.address(), list.access_level_str(), member); + } + } + } } Ok(()) diff --git a/src/schema.rs b/src/schema.rs index c8579e1..87663cd 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -2,6 +2,18 @@ use crate::data::Data; use failure::{err_msg, Error}; use std::collections::HashSet; +#[derive(serde_derive::Deserialize, Debug)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub(crate) struct Config { + allowed_mailing_lists_domains: HashSet, +} + +impl Config { + pub(crate) fn allowed_mailing_lists_domains(&self) -> &HashSet { + &self.allowed_mailing_lists_domains + } +} + #[derive(serde_derive::Deserialize, Debug)] #[serde(deny_unknown_fields)] pub(crate) struct Person { diff --git a/src/validate.rs b/src/validate.rs index a114ebc..45c0d0a 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -12,6 +12,7 @@ pub(crate) fn validate(data: &Data) -> Result<(), Error> { validate_list_email_addresses(data, &mut errors); validate_list_extra_people(data, &mut errors); validate_list_extra_teams(data, &mut errors); + validate_list_addresses(data, &mut errors); validate_discord_name(data, &mut errors); if !errors.is_empty() { @@ -46,7 +47,7 @@ fn validate_team_leads(data: &Data, errors: &mut Vec) { }); } -/// Ensure team members are people +/// Ensure t_eam members are people fn validate_team_members(data: &Data, errors: &mut Vec) { wrapper(data.teams(), errors, |team, errors| { wrapper(team.members(data)?.iter(), errors, |member, _| { @@ -142,6 +143,25 @@ fn validate_list_extra_teams(data: &Data, errors: &mut Vec) { }); } +/// Ensure the list addresses are correct +fn validate_list_addresses(data: &Data, errors: &mut Vec) { + let email_re = Regex::new(r"^[a-zA-Z0-9_\.-]+@([a-zA-Z0-9_\.-]+)$").unwrap(); + let config = data.config().allowed_mailing_lists_domains(); + wrapper(data.teams(), errors, |team, errors| { + wrapper(team.raw_lists().iter(), errors, |list, _| { + if let Some(captures) = email_re.captures(&list.address) { + if !config.contains(&captures[1]) { + bail!("list address on a domain we don't own: `{}`", list.address); + } + } else { + bail!("invalid list address: `{}`", list.address); + } + Ok(()) + }); + Ok(()) + }); +} + /// Ensure the Discord name is formatted properly fn validate_discord_name(data: &Data, errors: &mut Vec) { // https://discordapp.com/developers/docs/resources/user#usernames-and-nicknames -- cgit v1.2.3