From 02dab8a94dce1a93e8ca50232cf75597fee327b0 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Wed, 6 Feb 2019 19:27:17 +0100 Subject: add permissions support and perf permissions --- src/main.rs | 16 ++++++++++++++ src/permissions.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/schema.rs | 16 ++++++++++++++ src/static_api.rs | 16 ++++++++++++++ src/validate.rs | 33 +++++++++++++++++++++++++++-- 5 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 src/permissions.rs (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 1f0592e..7ea1fad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #![allow(clippy::new_ret_no_self)] mod data; +#[macro_use] +mod permissions; mod schema; mod static_api; mod validate; @@ -21,6 +23,11 @@ enum Cli { DumpTeam { name: String }, #[structopt(name = "dump-list", help = "print all the emails in a list")] DumpList { name: String }, + #[structopt( + name = "dump-permission", + help = "print all the people with a permission" + )] + DumpPermission { name: String }, } fn main() { @@ -68,6 +75,15 @@ fn run() -> Result<(), Error> { println!("{}", email); } } + Cli::DumpPermission { ref name } => { + if !crate::schema::Permissions::AVAILABLE.contains(&name.as_str()) { + failure::bail!("unknown permission: {}", name); + } + let allowed = crate::permissions::allowed_github_users(&data, name)?; + for github_username in &allowed { + println!("{}", github_username); + } + } } Ok(()) diff --git a/src/permissions.rs b/src/permissions.rs new file mode 100644 index 0000000..f181e21 --- /dev/null +++ b/src/permissions.rs @@ -0,0 +1,62 @@ +use crate::data::Data; +use failure::Error; +use std::collections::HashSet; + +#[macro_export] +macro_rules! permissions { + ($vis:vis struct $name:ident { $($key:ident,)* }) => { + #[derive(serde_derive::Deserialize, Debug)] + #[serde(rename_all = "kebab-case", deny_unknown_fields)] + $vis struct $name { + $( + #[serde(default)] + $key: bool, + )* + } + + impl Default for $name { + fn default() -> Self { + $name { + $($key: false,)* + } + } + } + + impl $name { + $vis const AVAILABLE: &'static [&'static str] = &[$(stringify!($key),)*]; + + $vis fn has(&self, permission: &str) -> bool { + $( + if permission == stringify!($key) { + return self.$key; + } + )* + false + } + + $vis fn has_any(&self) -> bool { + false $(|| self.$key)* + } + } + } +} + +pub(crate) fn allowed_github_users( + data: &Data, + permission: &str, +) -> Result, Error> { + let mut github_users = HashSet::new(); + for team in data.teams() { + if team.permissions().has(permission) { + for member in team.members(&data)? { + github_users.insert(member.to_string()); + } + } + } + for person in data.people() { + if person.permissions().has(permission) { + github_users.insert(person.github().to_string()); + } + } + Ok(github_users) +} diff --git a/src/schema.rs b/src/schema.rs index 96b1217..adb67a4 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -45,6 +45,8 @@ pub(crate) struct Person { #[serde(default)] email: EmailField, discord: Option, + #[serde(default)] + permissions: Permissions, } impl Person { @@ -78,6 +80,10 @@ impl Person { self.discord.as_ref().map(|e| e.as_str()) } + pub(crate) fn permissions(&self) -> &Permissions { + &self.permissions + } + pub(crate) fn validate(&self) -> Result<(), Error> { if let EmailField::Disabled(true) = &self.email { bail!("`email = true` is not valid (for person {})", self.github); @@ -96,6 +102,8 @@ pub(crate) struct Team { #[serde(default)] children: Vec, people: TeamPeople, + #[serde(default)] + permissions: Permissions, website: Option, #[serde(default)] lists: Vec, @@ -190,6 +198,10 @@ impl Team { } Ok(lists) } + + pub(crate) fn permissions(&self) -> &Permissions { + &self.permissions + } } #[derive(serde_derive::Deserialize, Debug)] @@ -203,6 +215,10 @@ struct TeamPeople { include_wg_leads: bool, } +permissions!(pub(crate) struct Permissions { + perf, +}); + pub(crate) struct DiscordInvite<'a> { pub(crate) url: &'a str, pub(crate) channel: &'a str, diff --git a/src/static_api.rs b/src/static_api.rs index 33bac41..24bd989 100644 --- a/src/static_api.rs +++ b/src/static_api.rs @@ -1,4 +1,5 @@ use crate::data::Data; +use crate::schema::Permissions; use failure::Error; use indexmap::IndexMap; use log::info; @@ -23,6 +24,7 @@ impl<'a> Generator<'a> { pub(crate) fn generate(&self) -> Result<(), Error> { self.generate_teams()?; self.generate_lists()?; + self.generate_permissions()?; Ok(()) } @@ -96,6 +98,20 @@ impl<'a> Generator<'a> { Ok(()) } + fn generate_permissions(&self) -> Result<(), Error> { + for perm in Permissions::AVAILABLE { + let mut github_users = crate::permissions::allowed_github_users(&self.data, perm)? + .into_iter() + .collect::>(); + github_users.sort(); + self.add( + &format!("v1/permissions/{}.json", perm), + &v1::Permission { github_users }, + )?; + } + Ok(()) + } + fn add(&self, path: &str, obj: &T) -> Result<(), Error> { info!("writing API object {}...", path); let dest = self.dest.join(path); diff --git a/src/validate.rs b/src/validate.rs index 6606064..cce801f 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1,5 +1,5 @@ use crate::data::Data; -use crate::schema::Email; +use crate::schema::{Email, Permissions}; use failure::{bail, ensure, Error}; use regex::Regex; use std::collections::HashSet; @@ -18,6 +18,7 @@ pub(crate) fn validate(data: &Data) -> Result<(), Error> { validate_list_addresses(data, &mut errors); validate_people_addresses(data, &mut errors); validate_discord_name(data, &mut errors); + validate_duplicate_permissions(data, &mut errors); if !errors.is_empty() { errors.sort(); @@ -117,7 +118,13 @@ fn validate_inactive_members(data: &Data, errors: &mut Vec) { all_members.difference(&active_members), errors, |person, _| { - bail!("person `{}` is not a member of any team", person); + if !data.person(person).unwrap().permissions().has_any() { + bail!( + "person `{}` is not a member of any team and has no permissions", + person + ); + } + Ok(()) }, ); } @@ -229,6 +236,28 @@ fn validate_discord_name(data: &Data, errors: &mut Vec) { }) } +/// Ensure members of teams with permissions don't explicitly have those permissions +fn validate_duplicate_permissions(data: &Data, errors: &mut Vec) { + wrapper(data.teams(), errors, |team, errors| { + wrapper(team.members(&data)?.iter(), errors, |member, _| { + let person = data.person(member).unwrap(); + for permission in Permissions::AVAILABLE { + if team.permissions().has(permission) && person.permissions().has(permission) { + bail!( + "user `{}` has the permission `{}` both explicitly and through \ + the `{}` team", + member, + permission, + team.name() + ); + } + } + Ok(()) + }); + Ok(()) + }); +} + fn wrapper(iter: I, errors: &mut Vec, mut func: F) where I: Iterator, -- cgit v1.2.3