vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit c2a324e5da24bf2b59b5ccb745335783b4ea633f
parent 77f95146d61a2759b8c2f49c06d9897bae4ab8e7
Author: Jeremy Lin <jeremy.lin@gmail.com>
Date:   Thu,  9 Apr 2020 01:42:27 -0700

Clean up domain whitelist logic

* Make `SIGNUPS_DOMAINS_WHITELIST` override the `SIGNUPS_ALLOWED` setting.
  Otherwise, a common pitfall is to set `SIGNUPS_DOMAINS_WHITELIST` without
  realizing that `SIGNUPS_ALLOWED=false` must also be set.

* Whitespace is now accepted in `SIGNUPS_DOMAINS_WHITELIST`. That is,
  `foo.com, bar.com` is now equivalent to `foo.com,bar.com`.

* Add validation on `SIGNUPS_DOMAINS_WHITELIST`. For example, `foo.com,`
  is rejected as containing an empty token.

Diffstat:
Msrc/api/core/accounts.rs | 11+++++++----
Msrc/config.rs | 36+++++++++++++++++++++++++++---------
2 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs @@ -68,7 +68,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { let mut user = match User::find_by_mail(&data.Email, &conn) { Some(user) => { if !user.password_hash.is_empty() { - if CONFIG.signups_allowed() { + if CONFIG.is_signup_allowed(&data.Email) { err!("User already exists") } else { err!("Registration not allowed or user already exists") @@ -89,14 +89,17 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { } user - } else if CONFIG.signups_allowed() { + } else if CONFIG.is_signup_allowed(&data.Email) { err!("Account with this email already exists") } else { err!("Registration not allowed or user already exists") } } None => { - if CONFIG.signups_allowed() || Invitation::take(&data.Email, &conn) || CONFIG.can_signup_user(&data.Email) { + // Order is important here; the invitation check must come first + // because the bitwarden_rs admin can invite anyone, regardless + // of other signup restrictions. + if Invitation::take(&data.Email, &conn) || CONFIG.is_signup_allowed(&data.Email) { User::new(data.Email.clone()) } else { err!("Registration not allowed or user already exists") @@ -371,7 +374,7 @@ fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: Db err!("Email already in use"); } - if !CONFIG.signups_allowed() && !CONFIG.can_signup_user(&data.NewEmail) { + if !CONFIG.is_signup_allowed(&data.NewEmail) { err!("Email cannot be changed to this address"); } diff --git a/src/config.rs b/src/config.rs @@ -112,6 +112,8 @@ macro_rules! make_config { )+)+ config.domain_set = _domain_set; + config.signups_domains_whitelist = config.signups_domains_whitelist.trim().to_lowercase(); + config } } @@ -263,7 +265,7 @@ make_config! { /// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0, /// otherwise it will delete them and they won't be downloaded again. disable_icon_download: bool, true, def, false; - /// Allow new signups |> Controls if new users can register. Note that while this is disabled, users could still be invited + /// Allow new signups |> Controls whether new users can register. Users can be invited by the bitwarden_rs admin even if this is disabled signups_allowed: bool, true, def, true; /// Require email verification on signups. This will prevent logins from succeeding until the address has been verified signups_verify: bool, true, def, false; @@ -271,9 +273,9 @@ make_config! { signups_verify_resend_time: u64, true, def, 3_600; /// If signups require email verification, limit how many emails are automatically sent when login is attempted (0 means no limit) signups_verify_resend_limit: u32, true, def, 6; - /// Allow signups only from this list of comma-separated domains + /// 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(); - /// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are disabled + /// 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. /// The changes only apply when a user changes their password. Not recommended to lower the value @@ -428,6 +430,11 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { err!("DOMAIN variable needs to contain the protocol (http, https). Use 'http[s]://bw.example.com' instead of 'bw.example.com'"); } + let whitelist = &cfg.signups_domains_whitelist; + if !whitelist.is_empty() && whitelist.split(',').any(|d| d.trim().is_empty()) { + err!("`SIGNUPS_DOMAINS_WHITELIST` contains empty tokens"); + } + if let Some(ref token) = cfg.admin_token { if token.trim().is_empty() && !cfg.disable_admin_token { err!("`ADMIN_TOKEN` is enabled but has an empty value. To enable the admin page without token, use `DISABLE_ADMIN_TOKEN`") @@ -551,18 +558,29 @@ impl Config { self.update_config(builder) } - pub fn can_signup_user(&self, email: &str) -> bool { + /// Tests whether an email's domain is in signups_domains_whitelist. + /// Returns false if no whitelist is set. + pub fn is_email_domain_whitelisted(&self, email: &str) -> bool { let e: Vec<&str> = email.rsplitn(2, '@').collect(); if e.len() != 2 || e[0].is_empty() || e[1].is_empty() { warn!("Failed to parse email address '{}'", email); return false; } + let email_domain = e[0]; + let whitelist = self.signups_domains_whitelist(); - // Allow signups if the whitelist is empty/not configured - // (it doesn't contain any domains), or if it matches at least - // one domain. - let whitelist_str = self.signups_domains_whitelist(); - ( whitelist_str.is_empty() && CONFIG.signups_allowed() )|| whitelist_str.split(',').filter(|s| !s.is_empty()).any(|d| d == e[0]) + !whitelist.is_empty() && whitelist.split(',').any(|d| d.trim() == email_domain) + } + + /// Tests whether signup is allowed for an email address, taking into + /// account the signups_allowed and signups_domains_whitelist settings. + pub fn is_signup_allowed(&self, email: &str) -> bool { + if !self.signups_domains_whitelist().is_empty() { + // The whitelist setting overrides the signups_allowed setting. + self.is_email_domain_whitelisted(email) + } else { + self.signups_allowed() + } } pub fn delete_user_config(&self) -> Result<(), Error> {