From bfe420a0180107e646d0f4a80a93004ed7a1aa61 Mon Sep 17 00:00:00 2001 From: Timshel Date: Wed, 15 Apr 2026 18:44:55 +0000 Subject: [PATCH] Dummy org Master password policy auth fix (#7097) Co-authored-by: Timshel --- src/api/admin.rs | 13 +++++++++++-- src/api/core/accounts.rs | 2 +- src/api/core/organizations.rs | 28 ++++++++++++++++++++-------- src/sso.rs | 2 +- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 1546676f..9a782046 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -30,6 +30,7 @@ use crate::{ error::{Error, MapResult}, http_client::make_http_request, mail, + sso::FAKE_SSO_IDENTIFIER, util::{ container_base_image, format_naive_datetime_local, get_active_web_release, get_display_size, is_running_in_container, parse_experimental_client_feature_flags, FeatureFlagFilter, NumberOrString, @@ -315,7 +316,11 @@ async fn invite_user(data: Json, _token: AdminToken, conn: DbConn) - async fn _generate_invite(user: &User, conn: &DbConn) -> EmptyResult { if CONFIG.mail_enabled() { - let org_id: OrganizationId = FAKE_ADMIN_UUID.to_string().into(); + let org_id: OrganizationId = if CONFIG.sso_enabled() { + FAKE_SSO_IDENTIFIER.into() + } else { + FAKE_ADMIN_UUID.into() + }; let member_id: MembershipId = FAKE_ADMIN_UUID.to_string().into(); mail::send_invite(user, org_id, member_id, &CONFIG.invitation_org_name(), None).await } else { @@ -518,7 +523,11 @@ async fn resend_user_invite(user_id: UserId, _token: AdminToken, conn: DbConn) - } if CONFIG.mail_enabled() { - let org_id: OrganizationId = FAKE_ADMIN_UUID.to_string().into(); + let org_id: OrganizationId = if CONFIG.sso_enabled() { + FAKE_SSO_IDENTIFIER.into() + } else { + FAKE_ADMIN_UUID.into() + }; let member_id: MembershipId = FAKE_ADMIN_UUID.to_string().into(); mail::send_invite(&user, org_id, member_id, &CONFIG.invitation_org_name(), None).await } else { diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 8841c184..fa6a3fd2 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -374,7 +374,7 @@ async fn post_set_password(data: Json, headers: Headers, conn: } if let Some(identifier) = data.org_identifier { - if identifier != crate::sso::FAKE_IDENTIFIER && identifier != crate::api::admin::FAKE_ADMIN_UUID { + if identifier != crate::sso::FAKE_SSO_IDENTIFIER && identifier != crate::api::admin::FAKE_ADMIN_UUID { let Some(org) = Organization::find_by_uuid(&identifier.into(), &conn).await else { err!("Failed to retrieve the associated organization") }; diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 254f60b4..a2448236 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -20,7 +20,8 @@ use crate::{ DbConn, }, mail, - util::{convert_json_key_lcase_first, get_uuid, NumberOrString}, + sso::FAKE_SSO_IDENTIFIER, + util::{convert_json_key_lcase_first, NumberOrString}, CONFIG, }; @@ -64,6 +65,7 @@ pub fn routes() -> Vec { post_org_import, list_policies, list_policies_token, + get_dummy_master_password_policy, get_master_password_policy, get_policy, put_policy, @@ -353,7 +355,7 @@ async fn get_user_collections(headers: Headers, conn: DbConn) -> Json { // The returned `Id` will then be passed to `get_master_password_policy` which will mainly ignore it #[get("/organizations//auto-enroll-status")] async fn get_auto_enroll_status(identifier: &str, headers: Headers, conn: DbConn) -> JsonResult { - let org = if identifier == crate::sso::FAKE_IDENTIFIER { + let org = if identifier == FAKE_SSO_IDENTIFIER { match Membership::find_main_user_org(&headers.user.uuid, &conn).await { Some(member) => Organization::find_by_uuid(&member.org_uuid, &conn).await, None => None, @@ -363,7 +365,7 @@ async fn get_auto_enroll_status(identifier: &str, headers: Headers, conn: DbConn }; let (id, identifier, rp_auto_enroll) = match org { - None => (get_uuid(), identifier.to_string(), false), + None => (identifier.to_string(), identifier.to_string(), false), Some(org) => ( org.uuid.to_string(), org.uuid.to_string(), @@ -924,7 +926,7 @@ async fn get_org_domain_sso_verified(data: Json, conn: DbConn) .collect::>() { v if !v.is_empty() => v, - _ => vec![(crate::sso::FAKE_IDENTIFIER.to_string(), crate::sso::FAKE_IDENTIFIER.to_string())], + _ => vec![(FAKE_SSO_IDENTIFIER.to_string(), FAKE_SSO_IDENTIFIER.to_string())], }; Ok(Json(json!({ @@ -1975,9 +1977,19 @@ async fn list_policies_token(org_id: OrganizationId, token: &str, conn: DbConn) }))) } -// Called during the SSO enrollment. -// Return the org policy if it exists, otherwise use the default one. -#[get("/organizations//policies/master-password", rank = 1)] +// Called during the SSO enrollment return the default policy +#[get("/organizations/vaultwarden-dummy-oidc-identifier/policies/master-password", rank = 1)] +fn get_dummy_master_password_policy() -> JsonResult { + let (enabled, data) = match CONFIG.sso_master_password_policy_value() { + Some(policy) if CONFIG.sso_enabled() => (true, policy.to_string()), + _ => (false, "null".to_string()), + }; + let policy = OrgPolicy::new(FAKE_SSO_IDENTIFIER.into(), OrgPolicyType::MasterPassword, enabled, data); + Ok(Json(policy.to_json())) +} + +// Called during the SSO enrollment return the org policy if it exists +#[get("/organizations//policies/master-password", rank = 2)] async fn get_master_password_policy(org_id: OrganizationId, _headers: OrgMemberHeaders, conn: DbConn) -> JsonResult { let policy = OrgPolicy::find_by_org_and_type(&org_id, OrgPolicyType::MasterPassword, &conn).await.unwrap_or_else(|| { @@ -1992,7 +2004,7 @@ async fn get_master_password_policy(org_id: OrganizationId, _headers: OrgMemberH Ok(Json(policy.to_json())) } -#[get("/organizations//policies/", rank = 2)] +#[get("/organizations//policies/", rank = 3)] async fn get_policy(org_id: OrganizationId, pol_type: i32, headers: AdminHeaders, conn: DbConn) -> JsonResult { if org_id != headers.org_id { err!("Organization not found", "Organization id's do not match"); diff --git a/src/sso.rs b/src/sso.rs index ee6d707a..2f56f3a6 100644 --- a/src/sso.rs +++ b/src/sso.rs @@ -17,7 +17,7 @@ use crate::{ CONFIG, }; -pub static FAKE_IDENTIFIER: &str = "VW_DUMMY_IDENTIFIER_FOR_OIDC"; +pub static FAKE_SSO_IDENTIFIER: &str = "vaultwarden-dummy-oidc-identifier"; static SSO_JWT_ISSUER: LazyLock = LazyLock::new(|| format!("{}|sso", CONFIG.domain_origin()));