From d75a80bd2dbe21e5a1eb2b0a6b18a9422441e071 Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Sun, 11 Apr 2021 22:57:17 -0400 Subject: [PATCH 1/4] Resolves dani-garcia/bitwarden_rs#981 * a user without 2fa trying to join a 2fa org will fail, but user gets an email to enable 2fa * a user disabling 2fa will be removed from 2fa orgs; user gets an email for each org * an org enabling 2fa policy will remove users without 2fa; users get an email --- src/api/core/organizations.rs | 33 +++++ src/api/core/two_factor/mod.rs | 20 ++- src/config.rs | 1 + src/db/models/org_policy.rs | 1 + src/db/models/organization.rs | 21 ++- src/mail.rs | 16 +++ .../email/send_2fa_removed_from_org.hbs | 9 ++ .../email/send_2fa_removed_from_org.html.hbs | 129 ++++++++++++++++++ 8 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 src/static/templates/email/send_2fa_removed_from_org.hbs create mode 100644 src/static/templates/email/send_2fa_removed_from_org.html.hbs diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 5698c187..e750841c 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -647,6 +647,21 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase p.enabled, + None => false, + }; + + if org_twofactor_policy_enabled && user_twofactor_disabled { + let org = Organization::find_by_uuid(&org, &conn).unwrap(); + // you haven't joined yet, but mail explains why you were unable to accept invitation + mail::send_2fa_removed_from_org(&claims.email, &org.name)?; + err!("Organization policy requires that you enable two Two-step Login begin joining.") + } + user_org.status = UserOrgStatus::Accepted as i32; user_org.save(&conn)?; } @@ -996,6 +1011,24 @@ fn put_policy(org_id: String, pol_type: i32, data: Json, _headers: A Some(pt) => pt, None => err!("Invalid policy type"), }; + + if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled { + + let org_list = UserOrganization::find_by_org(&org_id, &conn); + + for user_org in org_list.into_iter() { + let user_twofactor_disabled = TwoFactor::find_by_user(&user_org.user_uuid, &conn).is_empty(); + + if user_twofactor_disabled && user_org.atype < UserOrgType::Admin { + + let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); + let user = User::find_by_uuid(&user_org.user_uuid, &conn).unwrap(); + + mail::send_2fa_removed_from_org(&user.email, &org.name)?; + user_org.delete(&conn)?; + } + } + } let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) { Some(p) => p, diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index a3dfd319..c0764761 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -8,9 +8,10 @@ use crate::{ auth::Headers, crypto, db::{ - models::{TwoFactor, User}, + models::*, DbConn, }, + mail, }; pub mod authenticator; @@ -134,6 +135,23 @@ fn disable_twofactor(data: JsonUpcase, headers: Headers, c twofactor.delete(&conn)?; } + let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &conn).is_empty(); + + if twofactor_disabled { + let policy_type = OrgPolicyType::TwoFactorAuthentication; + let org_list = UserOrganization::find_by_user_and_policy(&user.uuid, policy_type, &conn); + + for user_org in org_list.into_iter() { + + if user_org.atype < UserOrgType::Admin { + let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); + + mail::send_2fa_removed_from_org(&user.email, &org.name)?; + user_org.delete(&conn)?; + } + } + } + Ok(Json(json!({ "Enabled": false, "Type": type_, diff --git a/src/config.rs b/src/config.rs index 86031c72..38f916e9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -842,6 +842,7 @@ where reg!("email/new_device_logged_in", ".html"); reg!("email/pw_hint_none", ".html"); reg!("email/pw_hint_some", ".html"); + reg!("email/send_2fa_removed_from_org", ".html"); reg!("email/send_org_invite", ".html"); reg!("email/twofactor_email", ".html"); reg!("email/verify_email", ".html"); diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index 0707eccc..7d9cb6fc 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -22,6 +22,7 @@ db_object! { #[derive(Copy, Clone)] #[derive(num_derive::FromPrimitive)] +#[derive(PartialEq)] pub enum OrgPolicyType { TwoFactorAuthentication = 0, MasterPassword = 1, diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 1eeb04d2..671ba120 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -2,7 +2,7 @@ use serde_json::Value; use std::cmp::Ordering; use num_traits::FromPrimitive; -use super::{CollectionUser, User, OrgPolicy}; +use super::{CollectionUser, User, OrgPolicy, OrgPolicyType}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -538,6 +538,25 @@ impl UserOrganization { }} } + pub fn find_by_user_and_policy(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> Vec { + db_run! { conn: { + users_organizations::table + .inner_join( + org_policies::table.on( + org_policies::org_uuid.eq(users_organizations::org_uuid) + .and(users_organizations::user_uuid.eq(user_uuid)) + .and(org_policies::atype.eq(policy_type as i32)) + .and(org_policies::enabled.eq(true))) + ) + .filter( + users_organizations::status.eq(UserOrgStatus::Confirmed as i32) + ) + .select(users_organizations::all_columns) + .load::(conn) + .unwrap_or_default().from_db() + }} + } + pub fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec { db_run! { conn: { users_organizations::table diff --git a/src/mail.rs b/src/mail.rs index cd9edd9e..b1e81a52 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -179,6 +179,22 @@ pub fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult { send_email(address, &subject, body_html, body_text) } +pub fn send_2fa_removed_from_org( + address: &str, + org_name: &str, +) -> EmptyResult { + + let (subject, body_html, body_text) = get_text( + "email/send_2fa_removed_from_org", + json!({ + "url": CONFIG.domain(), + "org_name": org_name, + }), + )?; + + send_email(address, &subject, body_html, body_text) +} + pub fn send_invite( address: &str, uuid: &str, diff --git a/src/static/templates/email/send_2fa_removed_from_org.hbs b/src/static/templates/email/send_2fa_removed_from_org.hbs new file mode 100644 index 00000000..b836ff31 --- /dev/null +++ b/src/static/templates/email/send_2fa_removed_from_org.hbs @@ -0,0 +1,9 @@ +Removed from {{{org_name}}} + +You have been removed from organization *{{org_name}}* because your account does not have Two-step Login enabled. + + +You can enable Two-step Login in your account settings. + +=== +Github: https://github.com/dani-garcia/bitwarden_rs diff --git a/src/static/templates/email/send_2fa_removed_from_org.html.hbs b/src/static/templates/email/send_2fa_removed_from_org.html.hbs new file mode 100644 index 00000000..56dfc191 --- /dev/null +++ b/src/static/templates/email/send_2fa_removed_from_org.html.hbs @@ -0,0 +1,129 @@ +Removed from {{{org_name}}} + + + + + + Bitwarden_rs + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + + + + +
+ You have been removed from organization {{org_name}} because your account does not have Two-step Login enabled. +
+ You can enable Two-step Login in your account settings. +
+
+ + + + + +
+
+ + From 1db37bf3d06543c890612ff88193813035763034 Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Mon, 12 Apr 2021 21:54:57 -0400 Subject: [PATCH 2/4] make error toast display detailed message replace invite accept error message with the one from upstream check if config mail is enabled --- src/api/core/organizations.rs | 14 +++++++------- src/api/core/two_factor/mod.rs | 8 +++++--- src/error.rs | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index e750841c..fbb27840 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -656,10 +656,8 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase, _headers: A if user_twofactor_disabled && user_org.atype < UserOrgType::Admin { - let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); - let user = User::find_by_uuid(&user_org.user_uuid, &conn).unwrap(); + if CONFIG.mail_enabled() { + let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); + let user = User::find_by_uuid(&user_org.user_uuid, &conn).unwrap(); - mail::send_2fa_removed_from_org(&user.email, &org.name)?; + mail::send_2fa_removed_from_org(&user.email, &org.name)?; + } user_org.delete(&conn)?; } } diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index c0764761..d1d9e2b4 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -11,7 +11,7 @@ use crate::{ models::*, DbConn, }, - mail, + mail, CONFIG, }; pub mod authenticator; @@ -144,9 +144,11 @@ fn disable_twofactor(data: JsonUpcase, headers: Headers, c for user_org in org_list.into_iter() { if user_org.atype < UserOrgType::Admin { - let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); - mail::send_2fa_removed_from_org(&user.email, &org.name)?; + if CONFIG.mail_enabled() { + let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); + mail::send_2fa_removed_from_org(&user.email, &org.name)?; + } user_org.delete(&conn)?; } } diff --git a/src/error.rs b/src/error.rs index a0b28a4b..25c18e10 100644 --- a/src/error.rs +++ b/src/error.rs @@ -166,7 +166,7 @@ fn _serialize(e: &impl serde::Serialize, _msg: &str) -> String { fn _api_error(_: &impl std::any::Any, msg: &str) -> String { let json = json!({ - "Message": "", + "Message": msg, "error": "", "error_description": "", "ValidationErrors": {"": [ msg ]}, From 89a68741d6c049e827e84dc224566d1a61dda1f7 Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Fri, 16 Apr 2021 14:49:59 -0400 Subject: [PATCH 3/4] ran cargo fmt --all --- src/api/core/organizations.rs | 16 +++++++--------- src/api/core/two_factor/mod.rs | 7 +------ src/mail.rs | 6 +----- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index cb53e204..54ec92eb 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -649,13 +649,13 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase p.enabled, - None => false, - }; + let org_twofactor_policy_enabled = + match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, policy, &conn) { + Some(p) => p.enabled, + None => false, + }; if org_twofactor_policy_enabled && user_twofactor_disabled { - err!("You cannot join this organization until you enable two-step login on your user account.") } @@ -1010,16 +1010,14 @@ fn put_policy( Some(pt) => pt, None => err!("Invalid policy type"), }; - - if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled { + if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled { let org_list = UserOrganization::find_by_org(&org_id, &conn); for user_org in org_list.into_iter() { let user_twofactor_disabled = TwoFactor::find_by_user(&user_org.user_uuid, &conn).is_empty(); if user_twofactor_disabled && user_org.atype < UserOrgType::Admin { - if CONFIG.mail_enabled() { let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); let user = User::find_by_uuid(&user_org.user_uuid, &conn).unwrap(); @@ -1028,7 +1026,7 @@ fn put_policy( } user_org.delete(&conn)?; } - } + } } let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) { diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index a51eb2c3..fb0ee593 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -7,10 +7,7 @@ use crate::{ api::{JsonResult, JsonUpcase, NumberOrString, PasswordData}, auth::Headers, crypto, - db::{ - models::*, - DbConn, - }, + db::{models::*, DbConn}, mail, CONFIG, }; @@ -136,9 +133,7 @@ fn disable_twofactor(data: JsonUpcase, headers: Headers, c let org_list = UserOrganization::find_by_user_and_policy(&user.uuid, policy_type, &conn); for user_org in org_list.into_iter() { - if user_org.atype < UserOrgType::Admin { - if CONFIG.mail_enabled() { let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); mail::send_2fa_removed_from_org(&user.email, &org.name)?; diff --git a/src/mail.rs b/src/mail.rs index e8583468..66c8812e 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -181,11 +181,7 @@ pub fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult { send_email(address, &subject, body_html, body_text) } -pub fn send_2fa_removed_from_org( - address: &str, - org_name: &str, -) -> EmptyResult { - +pub fn send_2fa_removed_from_org(address: &str, org_name: &str) -> EmptyResult { let (subject, body_html, body_text) = get_text( "email/send_2fa_removed_from_org", json!({ From cc021a47843a10b2923af82113a6d6a7045d2bdf Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Tue, 27 Apr 2021 21:48:07 -0400 Subject: [PATCH 4/4] project name and links in new email templates --- src/static/templates/email/send_2fa_removed_from_org.hbs | 2 +- src/static/templates/email/send_2fa_removed_from_org.html.hbs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/static/templates/email/send_2fa_removed_from_org.hbs b/src/static/templates/email/send_2fa_removed_from_org.hbs index b836ff31..95d41b97 100644 --- a/src/static/templates/email/send_2fa_removed_from_org.hbs +++ b/src/static/templates/email/send_2fa_removed_from_org.hbs @@ -6,4 +6,4 @@ You have been removed from organization *{{org_name}}* because your account does You can enable Two-step Login in your account settings. === -Github: https://github.com/dani-garcia/bitwarden_rs +Github: https://github.com/dani-garcia/vaultwarden diff --git a/src/static/templates/email/send_2fa_removed_from_org.html.hbs b/src/static/templates/email/send_2fa_removed_from_org.html.hbs index 56dfc191..e881fd99 100644 --- a/src/static/templates/email/send_2fa_removed_from_org.html.hbs +++ b/src/static/templates/email/send_2fa_removed_from_org.html.hbs @@ -4,7 +4,7 @@ Removed from {{{org_name}}} - Bitwarden_rs + Vaultwarden