Merge pull request #957 from jjlin/domain-whitelist

Domain whitelist cleanup and fixes
This commit is contained in:
Daniel García 2020-04-18 12:08:48 +02:00 committed by GitHub
commit 0de52c6c99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 15 deletions

View file

@ -68,7 +68,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
let mut user = match User::find_by_mail(&data.Email, &conn) { let mut user = match User::find_by_mail(&data.Email, &conn) {
Some(user) => { Some(user) => {
if !user.password_hash.is_empty() { if !user.password_hash.is_empty() {
if CONFIG.signups_allowed() { if CONFIG.is_signup_allowed(&data.Email) {
err!("User already exists") err!("User already exists")
} else { } else {
err!("Registration not allowed or user already exists") err!("Registration not allowed or user already exists")
@ -89,14 +89,17 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
} }
user user
} else if CONFIG.signups_allowed() { } else if CONFIG.is_signup_allowed(&data.Email) {
err!("Account with this email already exists") err!("Account with this email already exists")
} else { } else {
err!("Registration not allowed or user already exists") err!("Registration not allowed or user already exists")
} }
} }
None => { 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()) User::new(data.Email.clone())
} else { } else {
err!("Registration not allowed or user already exists") 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"); 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"); err!("Email cannot be changed to this address");
} }

View file

@ -485,7 +485,11 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade
let user = match User::find_by_mail(&email, &conn) { let user = match User::find_by_mail(&email, &conn) {
None => { None => {
if !CONFIG.invitations_allowed() { if !CONFIG.invitations_allowed() {
err!(format!("User email does not exist: {}", email)) err!(format!("User does not exist: {}", email))
}
if !CONFIG.signups_domains_whitelist().is_empty() && !CONFIG.is_email_domain_whitelisted(&email) {
err!("Email domain not eligible for invitations")
} }
if !CONFIG.mail_enabled() { if !CONFIG.mail_enabled() {

View file

@ -112,6 +112,8 @@ macro_rules! make_config {
)+)+ )+)+
config.domain_set = _domain_set; config.domain_set = _domain_set;
config.signups_domains_whitelist = config.signups_domains_whitelist.trim().to_lowercase();
config 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, /// $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. /// otherwise it will delete them and they won't be downloaded again.
disable_icon_download: bool, true, def, false; 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; signups_allowed: bool, true, def, true;
/// Require email verification on signups. This will prevent logins from succeeding until the address has been verified /// Require email verification on signups. This will prevent logins from succeeding until the address has been verified
signups_verify: bool, true, def, false; signups_verify: bool, true, def, false;
@ -271,9 +273,9 @@ make_config! {
signups_verify_resend_time: u64, true, def, 3_600; 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) /// 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; 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(); 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; invitations_allowed: bool, true, def, true;
/// Password iterations |> Number of server-side passwords hashing iterations. /// 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 /// 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'"); 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 let Some(ref token) = cfg.admin_token {
if token.trim().is_empty() && !cfg.disable_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."); println!("[WARNING] `ADMIN_TOKEN` is enabled but has an empty value, so the admin page will be disabled.");
@ -552,18 +559,29 @@ impl Config {
self.update_config(builder) 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(); let e: Vec<&str> = email.rsplitn(2, '@').collect();
if e.len() != 2 || e[0].is_empty() || e[1].is_empty() { if e.len() != 2 || e[0].is_empty() || e[1].is_empty() {
warn!("Failed to parse email address '{}'", email); warn!("Failed to parse email address '{}'", email);
return false; return false;
} }
let email_domain = e[0].to_lowercase();
let whitelist = self.signups_domains_whitelist();
// Allow signups if the whitelist is empty/not configured !whitelist.is_empty() && whitelist.split(',').any(|d| d.trim() == email_domain)
// (it doesn't contain any domains), or if it matches at least }
// one domain.
let whitelist_str = self.signups_domains_whitelist(); /// Tests whether signup is allowed for an email address, taking into
( whitelist_str.is_empty() && CONFIG.signups_allowed() )|| whitelist_str.split(',').filter(|s| !s.is_empty()).any(|d| d == e[0]) /// 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> { pub fn delete_user_config(&self) -> Result<(), Error> {