From 978f0092937c3a62f2de4ddc487137429754d3c0 Mon Sep 17 00:00:00 2001 From: Mathijs van Veluw Date: Tue, 27 Aug 2024 19:37:51 +0200 Subject: [PATCH] Allow Org Master-Pw policy enforcement (#4899) * Allow Org Master-Pw policy enforcement We didn't returned the master password policy for the user. If the `Require existing members to change their passwords` check was enabled this should trigger the login to show a change password dialog. All the master password policies are merged into one during the login response and it will contain the max values and all `true` values which are set by all the different orgs if a user is an accepted member. Fixes #4507 Signed-off-by: BlackDex * Use .reduce instead of .fold Signed-off-by: BlackDex --------- Signed-off-by: BlackDex --- src/api/identity.rs | 46 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index 93ef80bc..27f3eac6 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -135,6 +135,18 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult { Ok(Json(result)) } +#[derive(Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +struct MasterPasswordPolicy { + min_complexity: u8, + min_length: u32, + require_lower: bool, + require_upper: bool, + require_numbers: bool, + require_special: bool, + enforce_on_login: bool, +} + async fn _password_login( data: ConnectData, user_uuid: &mut Option, @@ -282,6 +294,36 @@ async fn _password_login( let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec); device.save(conn).await?; + // Fetch all valid Master Password Policies and merge them into one with all true's and larges numbers as one policy + let master_password_policies: Vec = + OrgPolicy::find_accepted_and_confirmed_by_user_and_active_policy( + &user.uuid, + OrgPolicyType::MasterPassword, + conn, + ) + .await + .into_iter() + .filter_map(|p| serde_json::from_str(&p.data).ok()) + .collect(); + + let master_password_policy = if !master_password_policies.is_empty() { + let mut mpp_json = json!(master_password_policies.into_iter().reduce(|acc, policy| { + MasterPasswordPolicy { + min_complexity: acc.min_complexity.max(policy.min_complexity), + min_length: acc.min_length.max(policy.min_length), + require_lower: acc.require_lower || policy.require_lower, + require_upper: acc.require_upper || policy.require_upper, + require_numbers: acc.require_numbers || policy.require_numbers, + require_special: acc.require_special || policy.require_special, + enforce_on_login: acc.enforce_on_login || policy.enforce_on_login, + } + })); + mpp_json["object"] = json!("masterPasswordPolicy"); + mpp_json + } else { + json!({"object": "masterPasswordPolicy"}) + }; + let mut result = json!({ "access_token": access_token, "expires_in": expires_in, @@ -297,9 +339,7 @@ async fn _password_login( "KdfParallelism": user.client_kdf_parallelism, "ResetMasterPassword": false, // TODO: Same as above "ForcePasswordReset": false, - "MasterPasswordPolicy": { - "object": "masterPasswordPolicy", - }, + "MasterPasswordPolicy": master_password_policy, "scope": scope, "unofficialServer": true,