summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPietro Albini <pietro@pietroalbini.org>2018-11-27 19:47:05 +0100
committerPietro Albini <pietro@pietroalbini.org>2018-11-27 19:47:05 +0100
commitef53b1f01acbda586424805d604f96ea00f7c981 (patch)
treea3835accaa4f101cb7a8418fd14677eddc64c876 /src
parent9e415774c8a7cf3c7428a817dcdaf89a43c717ed (diff)
validate list emails
Diffstat (limited to 'src')
-rw-r--r--src/data.rs23
-rw-r--r--src/main.rs9
-rw-r--r--src/schema.rs12
-rw-r--r--src/validate.rs22
4 files changed, 59 insertions, 7 deletions
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<String, Person>,
teams: HashMap<String, Team>,
+ 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<HashMap<String, List>, Error> {
let mut lists = HashMap::new();
for team in self.teams.values() {
@@ -79,3 +82,11 @@ impl Data {
self.people.values()
}
}
+
+fn load_file<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T, Error> {
+ 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
@@ -3,6 +3,18 @@ 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<String>,
+}
+
+impl Config {
+ pub(crate) fn allowed_mailing_lists_domains(&self) -> &HashSet<String> {
+ &self.allowed_mailing_lists_domains
+ }
+}
+
+#[derive(serde_derive::Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub(crate) struct Person {
name: String,
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<String>) {
});
}
-/// Ensure team members are people
+/// Ensure t_eam members are people
fn validate_team_members(data: &Data, errors: &mut Vec<String>) {
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<String>) {
});
}
+/// Ensure the list addresses are correct
+fn validate_list_addresses(data: &Data, errors: &mut Vec<String>) {
+ 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<String>) {
// https://discordapp.com/developers/docs/resources/user#usernames-and-nicknames