summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPietro Albini <pietro@pietroalbini.org>2019-02-06 19:27:17 +0100
committerPietro Albini <pietro@pietroalbini.org>2019-02-06 19:27:17 +0100
commit02dab8a94dce1a93e8ca50232cf75597fee327b0 (patch)
treebc0131bdf379de9ceb755dfede8b896207a1c677 /src
parent530fcd964b20efed36ec2172e608cbac019fa937 (diff)
add permissions support and perf permissions
Diffstat (limited to 'src')
-rw-r--r--src/main.rs16
-rw-r--r--src/permissions.rs62
-rw-r--r--src/schema.rs16
-rw-r--r--src/static_api.rs16
-rw-r--r--src/validate.rs33
5 files changed, 141 insertions, 2 deletions
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<HashSet<String>, 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<String>,
+ #[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<String>,
people: TeamPeople,
+ #[serde(default)]
+ permissions: Permissions,
website: Option<WebsiteData>,
#[serde(default)]
lists: Vec<TeamList>,
@@ -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::<Vec<_>>();
+ github_users.sort();
+ self.add(
+ &format!("v1/permissions/{}.json", perm),
+ &v1::Permission { github_users },
+ )?;
+ }
+ Ok(())
+ }
+
fn add<T: serde::Serialize>(&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<String>) {
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<String>) {
})
}
+/// Ensure members of teams with permissions don't explicitly have those permissions
+fn validate_duplicate_permissions(data: &Data, errors: &mut Vec<String>) {
+ 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<T, I, F>(iter: I, errors: &mut Vec<String>, mut func: F)
where
I: Iterator<Item = T>,