From d75a80bd2dbe21e5a1eb2b0a6b18a9422441e071 Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Sun, 11 Apr 2021 22:57:17 -0400 Subject: [PATCH] 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. +
+
+ + + + + +
+
+ +