From 479638c7818d33a4ce54a4c778f3a03af7b02853 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk Date: Sun, 27 Aug 2023 00:32:12 +0200 Subject: [PATCH] revoke user when enforcing 2fa policy --- src/api/admin.rs | 4 +- src/api/core/organizations.rs | 42 +++++------------- src/api/core/two_factor/mod.rs | 81 +++++++++++++++++++++++++++++----- 3 files changed, 83 insertions(+), 44 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 89d0c06b360..d14e2aea753 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -446,10 +446,10 @@ async fn enable_user(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyR } #[post("/users//remove-2fa")] -async fn remove_2fa(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { +async fn remove_2fa(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult { let mut user = get_user_or_404(uuid, &mut conn).await?; TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; - two_factor::enforce_2fa_policy(&user, &mut conn).await?; + two_factor::enforce_2fa_policy(&user, String::from(ACTING_ADMIN_USER), 14, &token.ip.ip, &mut conn).await?; user.totp_recover = None; user.save(&mut conn).await } diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 0bcf262ff7d..41a39cb951f 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -5,7 +5,7 @@ use serde_json::Value; use crate::{ api::{ - core::{log_event, CipherSyncData, CipherSyncType}, + core::{log_event, two_factor, CipherSyncData, CipherSyncType}, EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, JsonVec, Notify, NumberOrString, PasswordData, UpdateType, }, auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders}, @@ -1698,38 +1698,16 @@ async fn put_policy( None => err!("Invalid or unsupported policy type"), }; - // When enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA + // When enabling the TwoFactorAuthentication policy, revoke all members that do not have 2FA if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled { - for member in UserOrganization::find_by_org(org_id, &mut conn).await.into_iter() { - let user_twofactor_disabled = TwoFactor::find_by_user(&member.user_uuid, &mut conn).await.is_empty(); - - // Policy only applies to non-Owner/non-Admin members who have accepted joining the org - // Invited users still need to accept the invite and will get an error when they try to accept the invite. - if user_twofactor_disabled - && member.atype < UserOrgType::Admin - && member.status != UserOrgStatus::Invited as i32 - { - if CONFIG.mail_enabled() { - let org = Organization::find_by_uuid(&member.org_uuid, &mut conn).await.unwrap(); - let user = User::find_by_uuid(&member.user_uuid, &mut conn).await.unwrap(); - - mail::send_2fa_removed_from_org(&user.email, &org.name).await?; - } - - log_event( - EventType::OrganizationUserRemoved as i32, - &member.uuid, - org_id, - headers.user.uuid.clone(), - headers.device.atype, - &headers.ip.ip, - &mut conn, - ) - .await; - - member.delete(&mut conn).await?; - } - } + two_factor::enforce_2fa_policy_for_org( + org_id, + headers.user.uuid.clone(), + headers.device.atype, + &headers.ip.ip, + &mut conn, + ) + .await?; } // When enabling the SingleOrg policy, remove this org's members that are members of other orgs diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs index e544506807d..fa23abfce81 100644 --- a/src/api/core/two_factor/mod.rs +++ b/src/api/core/two_factor/mod.rs @@ -5,7 +5,10 @@ use rocket::Route; use serde_json::Value; use crate::{ - api::{core::log_user_event, EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordData}, + api::{ + core::{log_event, log_user_event}, + EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordData, + }, auth::{ClientHeaders, Headers}, crypto, db::{models::*, DbConn, DbPool}, @@ -96,7 +99,7 @@ async fn recover(data: JsonUpcase, client_headers: ClientHeade // Remove all twofactors from the user TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; - enforce_2fa_policy(&user, &mut conn).await?; + enforce_2fa_policy(&user, user.uuid.clone(), client_headers.device_type, &client_headers.ip.ip, &mut conn).await?; log_user_event( EventType::UserRecovered2fa as i32, @@ -147,7 +150,7 @@ async fn disable_twofactor(data: JsonUpcase, headers: Head } if TwoFactor::find_by_user(&user.uuid, &mut conn).await.is_empty() { - enforce_2fa_policy(&user, &mut conn).await?; + enforce_2fa_policy(&user, user.uuid.clone(), headers.device.atype, &headers.ip.ip, &mut conn).await?; } Ok(Json(json!({ @@ -162,17 +165,75 @@ async fn disable_twofactor_put(data: JsonUpcase, headers: disable_twofactor(data, headers, conn).await } -pub async fn enforce_2fa_policy(user: &User, conn: &mut DbConn) -> EmptyResult { - for user_org in UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, conn) - .await - .into_iter() +pub async fn enforce_2fa_policy( + user: &User, + act_uuid: String, + device_type: i32, + ip: &std::net::IpAddr, + conn: &mut DbConn, +) -> EmptyResult { + for mut member in + UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, conn) + .await + .into_iter() { - if user_org.atype < UserOrgType::Admin { + // Policy only applies to non-Owner/non-Admin members who have accepted joining the org + if member.atype < UserOrgType::Admin { + if CONFIG.mail_enabled() { + let org = Organization::find_by_uuid(&member.org_uuid, conn).await.unwrap(); + mail::send_2fa_removed_from_org(&user.email, &org.name).await?; + } + member.revoke(); + member.save(conn).await?; + + log_event( + EventType::OrganizationUserRevoked as i32, + &member.uuid, + &member.org_uuid, + act_uuid.clone(), + device_type, + ip, + conn, + ) + .await; + } + } + + Ok(()) +} + +pub async fn enforce_2fa_policy_for_org( + org_uuid: &str, + act_uuid: String, + device_type: i32, + ip: &std::net::IpAddr, + conn: &mut DbConn, +) -> EmptyResult { + let org = Organization::find_by_uuid(org_uuid, conn).await.unwrap(); + for mut member in UserOrganization::find_by_org(org_uuid, conn).await.into_iter() { + // Policy only applies to non-Owner/non-Admin members who have accepted joining the org + // Invited users still need to accept the invite and will get an error when they try to accept the invite. + if member.atype < UserOrgType::Admin + && member.status != UserOrgStatus::Invited as i32 + && TwoFactor::find_by_user(&member.user_uuid, conn).await.is_empty() + { if CONFIG.mail_enabled() { - let org = Organization::find_by_uuid(&user_org.org_uuid, conn).await.unwrap(); + let user = User::find_by_uuid(&member.user_uuid, conn).await.unwrap(); mail::send_2fa_removed_from_org(&user.email, &org.name).await?; } - user_org.delete(conn).await?; + member.revoke(); + member.save(conn).await?; + + log_event( + EventType::OrganizationUserRevoked as i32, + &member.uuid, + org_uuid, + act_uuid.clone(), + device_type, + ip, + conn, + ) + .await; } }