From 570d6c8bf97d6c554a9f5265c9cc9aa4e8482f24 Mon Sep 17 00:00:00 2001
From: Jeremy Lin <jeremy.lin@gmail.com>
Date: Wed, 5 Aug 2020 22:35:29 -0700
Subject: [PATCH] Add support for restricting org creation to certain users

---
 .env.template                 |  8 ++++++++
 src/api/core/organizations.rs |  4 ++++
 src/config.rs                 | 24 ++++++++++++++++++++++++
 3 files changed, 36 insertions(+)

diff --git a/.env.template b/.env.template
index 4cd3ed25..e964873a 100644
--- a/.env.template
+++ b/.env.template
@@ -118,6 +118,14 @@
 ## even if SIGNUPS_ALLOWED is set to false
 # SIGNUPS_DOMAINS_WHITELIST=example.com,example.net,example.org
 
+## Controls which users can create new orgs.
+## Blank or 'all' means all users can create orgs (this is the default):
+# ORG_CREATION_USERS=
+## 'none' means no users can create orgs:
+# ORG_CREATION_USERS=none
+## A comma-separated list means only those users can create orgs:
+# ORG_CREATION_USERS=admin1@example.com,admin2@example.com
+
 ## Token for the admin interface, preferably use a long random string
 ## One option is to use 'openssl rand -base64 48'
 ## If not set, the admin panel is disabled
diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
index b4ec4d8e..322ed209 100644
--- a/src/api/core/organizations.rs
+++ b/src/api/core/organizations.rs
@@ -76,6 +76,10 @@ struct NewCollectionData {
 
 #[post("/organizations", data = "<data>")]
 fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn) -> JsonResult {
+    if !CONFIG.is_org_creation_allowed(&headers.user.email) {
+        err!("User not allowed to create organizations")
+    }
+
     let data: OrgData = data.into_inner().data;
 
     let org = Organization::new(data.Name, data.BillingEmail);
diff --git a/src/config.rs b/src/config.rs
index 1192f75d..33b2fc3a 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -115,6 +115,7 @@ macro_rules! make_config {
                 config.domain_set = _domain_set;
 
                 config.signups_domains_whitelist = config.signups_domains_whitelist.trim().to_lowercase();
+                config.org_creation_users = config.org_creation_users.trim().to_lowercase();
 
                 config
             }
@@ -276,6 +277,9 @@ make_config! {
         signups_verify_resend_limit: u32, true, def,    6;
         /// Email domain whitelist |> Allow signups only from this list of comma-separated domains, even when signups are otherwise disabled
         signups_domains_whitelist: String, true, def,   "".to_string();
+        /// Org creation users |> Allow org creation only by this list of comma-separated user emails.
+        /// Blank or 'all' means all users can create orgs; 'none' means no users can create orgs.
+        org_creation_users:     String, true,   def,    "".to_string();
         /// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are otherwise disabled
         invitations_allowed:    bool,   true,   def,    true;
         /// Password iterations |> Number of server-side passwords hashing iterations.
@@ -442,6 +446,13 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
         err!("`SIGNUPS_DOMAINS_WHITELIST` contains empty tokens");
     }
 
+    let org_creation_users = cfg.org_creation_users.trim().to_lowercase();
+    if !(org_creation_users.is_empty() || org_creation_users == "all" || org_creation_users == "none") {
+        if org_creation_users.split(',').any(|u| !u.contains('@')) {
+            err!("`ORG_CREATION_USERS` contains invalid email addresses");
+        }
+    }
+
     if let Some(ref token) = cfg.admin_token {
         if token.trim().is_empty() && !cfg.disable_admin_token {
             println!("[WARNING] `ADMIN_TOKEN` is enabled but has an empty value, so the admin page will be disabled.");
@@ -592,6 +603,19 @@ impl Config {
         }
     }
 
+    /// Tests whether the specified user is allowed to create an organization.
+    pub fn is_org_creation_allowed(&self, email: &str) -> bool {
+        let users = self.org_creation_users();
+        if users == "" || users == "all" {
+            true
+        } else if users == "none" {
+            false
+        } else {
+            let email = email.to_lowercase();
+            users.split(',').any(|u| u.trim() == email)
+        }
+    }
+
     pub fn delete_user_config(&self) -> Result<(), Error> {
         crate::util::delete_file(&CONFIG_FILE)?;