Skip to content

Commit

Permalink
revoke user when enforcing 2fa policy
Browse files Browse the repository at this point in the history
  • Loading branch information
stefan0xC committed Aug 26, 2023
1 parent f363a63 commit e626392
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 36 deletions.
4 changes: 2 additions & 2 deletions src/api/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,10 +446,10 @@ async fn enable_user(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyR
}

#[post("/users/<uuid>/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
}
Expand Down
31 changes: 7 additions & 24 deletions src/api/core/organizations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -1698,36 +1698,19 @@ 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 this org's members that do 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,
if TwoFactor::find_by_user(&member.user_uuid, &mut conn).await.is_empty() {
let user = User::find_by_uuid(&member.user_uuid, &mut conn).await.unwrap();
two_factor::enforce_2fa_policy(
&user,
headers.user.uuid.clone(),
headers.device.atype,
&headers.ip.ip,
&mut conn,
)
.await;

member.delete(&mut conn).await?;
.await?;
}
}
}
Expand Down
44 changes: 34 additions & 10 deletions src/api/core/two_factor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -96,7 +99,7 @@ async fn recover(data: JsonUpcase<RecoverTwoFactor>, 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,
Expand Down Expand Up @@ -147,7 +150,7 @@ async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, 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!({
Expand All @@ -162,17 +165,38 @@ async fn disable_twofactor_put(data: JsonUpcase<DisableTwoFactorData>, 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
// 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 {
if CONFIG.mail_enabled() {
let org = Organization::find_by_uuid(&user_org.org_uuid, conn).await.unwrap();
let org = Organization::find_by_uuid(&member.org_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::OrganizationUserRemoved as i32,
&member.uuid,
&member.org_uuid,
act_uuid.clone(),
device_type,
&ip,
conn,
)
.await;
}
}

Expand Down

0 comments on commit e626392

Please sign in to comment.