commit d75a80bd2dbe21e5a1eb2b0a6b18a9422441e071
parent b268c3dd1cfda78f113cc5c3bf06e08324590379
Author: Olivier Martin <olivier.martin@illogika.com>
Date: Sun, 11 Apr 2021 22:57:17 -0400
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
Diffstat:
8 files changed, 228 insertions(+), 2 deletions(-)
diff --git 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<AcceptD
err!("User already accepted the invitation")
}
+ let user_twofactor_disabled = TwoFactor::find_by_user(&user_org.user_uuid, &conn).is_empty();
+
+ let policy = OrgPolicyType::TwoFactorAuthentication as i32;
+ 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 {
+ 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<PolicyData>, _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
@@ -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<DisableTwoFactorData>, 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
@@ -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
@@ -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
@@ -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<Self> {
+ 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::<UserOrganizationDb>(conn)
+ .unwrap_or_default().from_db()
+ }}
+ }
+
pub fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: {
users_organizations::table
diff --git 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
@@ -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
@@ -0,0 +1,129 @@
+Removed from {{{org_name}}}
+<!---------------->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
+ <head>
+ <meta name="viewport" content="width=device-width" />
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <title>Bitwarden_rs</title>
+ </head>
+ <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6">
+ <style type="text/css">
+ body {
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ box-sizing: border-box;
+ font-size: 16px;
+ color: #333;
+ line-height: 25px;
+ -webkit-font-smoothing: antialiased;
+ -webkit-text-size-adjust: none;
+ }
+ body * {
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ box-sizing: border-box;
+ font-size: 16px;
+ color: #333;
+ line-height: 25px;
+ -webkit-font-smoothing: antialiased;
+ -webkit-text-size-adjust: none;
+ }
+ img {
+ max-width: 100%;
+ border: none;
+ }
+ body {
+ -webkit-font-smoothing: antialiased;
+ -webkit-text-size-adjust: none;
+ width: 100% !important;
+ height: 100%;
+ line-height: 25px;
+ }
+ body {
+ background-color: #f6f6f6;
+ }
+ @media only screen and (max-width: 600px) {
+ body {
+ padding: 0 !important;
+ }
+ .container {
+ padding: 0 !important;
+ width: 100% !important;
+ }
+ .container-table {
+ padding: 0 !important;
+ width: 100% !important;
+ }
+ .content {
+ padding: 0 0 10px 0 !important;
+ }
+ .content-wrap {
+ padding: 10px !important;
+ }
+ .invoice {
+ width: 100% !important;
+ }
+ .main {
+ border-right: none !important;
+ border-left: none !important;
+ border-radius: 0 !important;
+ }
+ .logo {
+ padding-top: 10px !important;
+ }
+ .footer {
+ margin-top: 10px !important;
+ }
+ .indented {
+ padding-left: 10px;
+ }
+ }
+ </style>
+ <table class="body-wrap" cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; width: 100%;" bgcolor="#f6f6f6">
+ <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
+ <td valign="middle" class="aligncenter middle logo" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; padding: 20px 0 10px;" align="center">
+ <img src="{{url}}/bwrs_static/logo-gray.png" alt="" width="250" height="39" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" />
+ </td>
+ </tr>
+ <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
+ <td class="container" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;" valign="top">
+ <table cellpadding="0" cellspacing="0" class="container-table" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: max-content;">
+ <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
+ <td class="content" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; display: block; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 0; line-height: 0; margin: 0 auto; max-width: 600px; padding-bottom: 20px;" valign="top">
+ <table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; margin: 0; -webkit-text-size-adjust: none; border: 1px solid #e9e9e9; border-radius: 3px;" bgcolor="white">
+ <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
+ <td class="content-wrap" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 20px; -webkit-text-size-adjust: none;" valign="top">
+ <table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
+ <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
+ <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
+ You have been removed from organization <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{org_name}}</b> because your account does not have Two-step Login enabled.
+ </td>
+ </tr>
+ <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
+ <td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
+ You can enable Two-step Login in your account settings.
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table class="footer" cellpadding="0" cellspacing="0" width="100%" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; width: 100%;">
+ <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
+ <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top">
+ <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;">
+ <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;">
+ <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>