Compare commits

..

4 Commits

Author SHA1 Message Date
Timshel
3f010a50af Change OIDC dummy identifier (#6263)
* Change OIDC dummy identifier

* Update src/sso.rs

Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>

* Use Org uuid as identifier

---------

Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>
Co-authored-by: Mathijs van Veluw <black.dex@gmail.com>
2025-10-13 21:28:37 +02:00
Timshel
e83faad8d2 Fix sso_user dropped on User::save (#6262)
* Admin delete SSO association prompt

* User.save don't use replace_into

* User.save use upsert with sqlite

* User.save use upsert with mysql
2025-10-13 21:25:53 +02:00
Stefan Melmuk
a79cd40ea9 improve permission check for collections (#6278) 2025-10-13 21:14:53 +02:00
Stefan Melmuk
b1d84298cc update web vault to v2025.9.1 and allow new policy (#6340)
* update web-vault to v2025.9.1

* allow new card removal policy
2025-10-13 20:54:24 +02:00
11 changed files with 48 additions and 45 deletions

View File

@@ -1,6 +1,6 @@
---
vault_version: "v2025.8.0"
vault_image_digest: "sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d"
vault_version: "v2025.9.1"
vault_image_digest: "sha256:15a126ca967cd2efc4c9625fec49f0b972a3f7d7d81d7770bb0a2502d5e4b8a4"
# Cross Compile Docker Helper Scripts v1.6.1
# We use the linux/amd64 platform shell scripts since there is no difference between the different platform scripts
# https://github.com/tonistiigi/xx | https://hub.docker.com/r/tonistiigi/xx/tags

View File

@@ -19,15 +19,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull docker.io/vaultwarden/web-vault:v2025.8.0
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.8.0
# [docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d]
# $ docker pull docker.io/vaultwarden/web-vault:v2025.9.1
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.9.1
# [docker.io/vaultwarden/web-vault@sha256:15a126ca967cd2efc4c9625fec49f0b972a3f7d7d81d7770bb0a2502d5e4b8a4]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d
# [docker.io/vaultwarden/web-vault:v2025.8.0]
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:15a126ca967cd2efc4c9625fec49f0b972a3f7d7d81d7770bb0a2502d5e4b8a4
# [docker.io/vaultwarden/web-vault:v2025.9.1]
#
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d AS vault
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:15a126ca967cd2efc4c9625fec49f0b972a3f7d7d81d7770bb0a2502d5e4b8a4 AS vault
########################## ALPINE BUILD IMAGES ##########################
## NOTE: The Alpine Base Images do not support other platforms then linux/amd64

View File

@@ -19,15 +19,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull docker.io/vaultwarden/web-vault:v2025.8.0
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.8.0
# [docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d]
# $ docker pull docker.io/vaultwarden/web-vault:v2025.9.1
# $ docker image inspect --format "{{.RepoDigests}}" docker.io/vaultwarden/web-vault:v2025.9.1
# [docker.io/vaultwarden/web-vault@sha256:15a126ca967cd2efc4c9625fec49f0b972a3f7d7d81d7770bb0a2502d5e4b8a4]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d
# [docker.io/vaultwarden/web-vault:v2025.8.0]
# $ docker image inspect --format "{{.RepoTags}}" docker.io/vaultwarden/web-vault@sha256:15a126ca967cd2efc4c9625fec49f0b972a3f7d7d81d7770bb0a2502d5e4b8a4
# [docker.io/vaultwarden/web-vault:v2025.9.1]
#
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:41c2b51c87882248f405d5a0ab37210d2672a312ec5d4f3b9afcdbbe8eb9d57d AS vault
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@sha256:15a126ca967cd2efc4c9625fec49f0b972a3f7d7d81d7770bb0a2502d5e4b8a4 AS vault
########################## Cross Compile Docker Helper Scripts ##########################
## We use the linux/amd64 no matter which Build Platform, since these are all bash scripts

View File

@@ -367,7 +367,7 @@ async fn post_set_password(data: Json<SetPasswordData>, headers: Headers, mut co
if let Some(identifier) = data.org_identifier {
if identifier != crate::sso::FAKE_IDENTIFIER {
let org = match Organization::find_by_name(&identifier, &mut conn).await {
let org = match Organization::find_by_uuid(&identifier.into(), &mut conn).await {
None => err!("Failed to retrieve the associated organization"),
Some(org) => org,
};

View File

@@ -773,8 +773,8 @@ async fn post_collections_update(
err!("Cipher doesn't exist")
};
if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await {
err!("Cipher is not write accessible")
if !cipher.is_in_editable_collection_by_user(&headers.user.uuid, &mut conn).await {
err!("Collection cannot be changed")
}
let posted_collections = HashSet::<CollectionId>::from_iter(data.collection_ids);
@@ -850,8 +850,8 @@ async fn post_collections_admin(
err!("Cipher doesn't exist")
};
if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await {
err!("Cipher is not write accessible")
if !cipher.is_in_editable_collection_by_user(&headers.user.uuid, &mut conn).await {
err!("Collection cannot be changed")
}
let posted_collections = HashSet::<CollectionId>::from_iter(data.collection_ids);

View File

@@ -339,7 +339,7 @@ async fn get_user_collections(headers: Headers, mut conn: DbConn) -> Json<Value>
}
// Called during the SSO enrollment
// The `identifier` should be the value returned by `get_org_domain_sso_details`
// The `identifier` should be the value returned by `get_org_domain_sso_verified`
// The returned `Id` will then be passed to `get_master_password_policy` which will mainly ignore it
#[get("/organizations/<identifier>/auto-enroll-status")]
async fn get_auto_enroll_status(identifier: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
@@ -349,7 +349,7 @@ async fn get_auto_enroll_status(identifier: &str, headers: Headers, mut conn: Db
None => None,
}
} else {
Organization::find_by_name(identifier, &mut conn).await
Organization::find_by_uuid(&identifier.into(), &mut conn).await
};
let (id, identifier, rp_auto_enroll) = match org {
@@ -977,17 +977,17 @@ async fn get_org_domain_sso_verified(data: Json<OrgDomainDetails>, mut conn: DbC
let identifiers = match Organization::find_org_user_email(&data.email, &mut conn)
.await
.into_iter()
.map(|o| o.name)
.collect::<Vec<String>>()
.map(|o| (o.name, o.uuid.to_string()))
.collect::<Vec<(String, String)>>()
{
v if !v.is_empty() => v,
_ => vec![crate::sso::FAKE_IDENTIFIER.to_string()],
_ => vec![(crate::sso::FAKE_IDENTIFIER.to_string(), crate::sso::FAKE_IDENTIFIER.to_string())],
};
Ok(Json(json!({
"object": "list",
"data": identifiers.into_iter().map(|identifier| json!({
"organizationName": identifier, // appear unused
"data": identifiers.into_iter().map(|(name, identifier)| json!({
"organizationName": name, // appear unused
"organizationIdentifier": identifier,
"domainName": CONFIG.domain(), // appear unused
})).collect::<Vec<Value>>()

View File

@@ -717,6 +717,15 @@ impl Cipher {
}
}
// used for checking if collection can be edited (only if user has access to a collection they
// can write to and also passwords are not hidden to prevent privilege escalation)
pub async fn is_in_editable_collection_by_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
match self.get_access_restrictions(user_uuid, None, conn).await {
Some((read_only, hide_passwords, manage)) => (!read_only && !hide_passwords) || manage,
None => false,
}
}
pub async fn is_accessible_to_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
self.get_access_restrictions(user_uuid, None, conn).await.is_some()
}

View File

@@ -39,6 +39,7 @@ pub enum OrgPolicyType {
// AutomaticAppLogIn = 12,
// FreeFamiliesSponsorshipPolicy = 13,
RemoveUnlockWithPin = 14,
RestrictedItemTypes = 15,
}
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs#L5

View File

@@ -283,24 +283,17 @@ impl User {
self.updated_at = Utc::now().naive_utc();
db_run! {conn:
sqlite, mysql {
match diesel::replace_into(users::table)
.values(UserDb::to_db(self))
.execute(conn)
{
Ok(_) => Ok(()),
// Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(users::table)
.filter(users::uuid.eq(&self.uuid))
.set(UserDb::to_db(self))
mysql {
let value = UserDb::to_db(self);
diesel::insert_into(users::table)
.values(&value)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_update()
.set(&value)
.execute(conn)
.map_res("Error saving user")
}
Err(e) => Err(e.into()),
}.map_res("Error saving user")
}
postgresql {
postgresql, sqlite {
let value = UserDb::to_db(self);
diesel::insert_into(users::table) // Insert or update
.values(&value)

View File

@@ -19,7 +19,7 @@ use crate::{
CONFIG,
};
pub static FAKE_IDENTIFIER: &str = "Vaultwarden";
pub static FAKE_IDENTIFIER: &str = "VW_DUMMY_IDENTIFIER_FOR_OIDC";
static AC_CACHE: Lazy<Cache<OIDCState, AuthenticatedUser>> =
Lazy::new(|| Cache::builder().max_capacity(1000).time_to_live(Duration::from_secs(10 * 60)).build());

View File

@@ -33,11 +33,11 @@ function deleteSSOUser(event) {
alert("Required parameters not found!");
return false;
}
const input_email = prompt(`To delete user "${email}", please type the email below`);
const input_email = prompt(`To delete user "${email}" SSO association, please type the email below`);
if (input_email != null) {
if (input_email == email) {
_delete(`${BASE_URL}/admin/users/${id}/sso`,
"User SSO Associtation deleted correctly",
"User SSO association deleted correctly",
"Error deleting user SSO association"
);
} else {