mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2024-11-28 17:09:14 +03:00
prevent side effects if groups are disabled (#4265)
This commit is contained in:
parent
5e46a43306
commit
1b801406d6
4 changed files with 226 additions and 126 deletions
|
@ -1788,15 +1788,22 @@ impl CipherSyncData {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Generate a HashMap with the collections_uuid as key and the CollectionGroup record
|
// Generate a HashMap with the collections_uuid as key and the CollectionGroup record
|
||||||
let user_collections_groups: HashMap<String, CollectionGroup> = CollectionGroup::find_by_user(user_uuid, conn)
|
let user_collections_groups: HashMap<String, CollectionGroup> = if CONFIG.org_groups_enabled() {
|
||||||
.await
|
CollectionGroup::find_by_user(user_uuid, conn)
|
||||||
.into_iter()
|
.await
|
||||||
.map(|collection_group| (collection_group.collections_uuid.clone(), collection_group))
|
.into_iter()
|
||||||
.collect();
|
.map(|collection_group| (collection_group.collections_uuid.clone(), collection_group))
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
HashMap::new()
|
||||||
|
};
|
||||||
|
|
||||||
// Get all organizations that the user has full access to via group assignment
|
// Get all organizations that the user has full access to via group assignment
|
||||||
let user_group_full_access_for_organizations: HashSet<String> =
|
let user_group_full_access_for_organizations: HashSet<String> = if CONFIG.org_groups_enabled() {
|
||||||
Group::gather_user_organizations_full_access(user_uuid, conn).await.into_iter().collect();
|
Group::gather_user_organizations_full_access(user_uuid, conn).await.into_iter().collect()
|
||||||
|
} else {
|
||||||
|
HashSet::new()
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
cipher_attachments,
|
cipher_attachments,
|
||||||
|
|
|
@ -294,7 +294,7 @@ async fn post_organization(
|
||||||
async fn get_user_collections(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
async fn get_user_collections(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"Data":
|
"Data":
|
||||||
Collection::find_by_user_uuid(headers.user.uuid.clone(), &mut conn).await
|
Collection::find_by_user_uuid(headers.user.uuid, &mut conn).await
|
||||||
.iter()
|
.iter()
|
||||||
.map(Collection::to_json)
|
.map(Collection::to_json)
|
||||||
.collect::<Value>(),
|
.collect::<Value>(),
|
||||||
|
|
|
@ -426,6 +426,9 @@ impl Cipher {
|
||||||
cipher_sync_data: Option<&CipherSyncData>,
|
cipher_sync_data: Option<&CipherSyncData>,
|
||||||
conn: &mut DbConn,
|
conn: &mut DbConn,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
if !CONFIG.org_groups_enabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if let Some(ref org_uuid) = self.organization_uuid {
|
if let Some(ref org_uuid) = self.organization_uuid {
|
||||||
if let Some(cipher_sync_data) = cipher_sync_data {
|
if let Some(cipher_sync_data) = cipher_sync_data {
|
||||||
return cipher_sync_data.user_group_full_access_for_organizations.get(org_uuid).is_some();
|
return cipher_sync_data.user_group_full_access_for_organizations.get(org_uuid).is_some();
|
||||||
|
@ -521,6 +524,9 @@ impl Cipher {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_group_collections_access_flags(&self, user_uuid: &str, conn: &mut DbConn) -> Vec<(bool, bool)> {
|
async fn get_group_collections_access_flags(&self, user_uuid: &str, conn: &mut DbConn) -> Vec<(bool, bool)> {
|
||||||
|
if !CONFIG.org_groups_enabled() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
db_run! {conn: {
|
db_run! {conn: {
|
||||||
ciphers::table
|
ciphers::table
|
||||||
.filter(ciphers::uuid.eq(&self.uuid))
|
.filter(ciphers::uuid.eq(&self.uuid))
|
||||||
|
@ -602,50 +608,84 @@ impl Cipher {
|
||||||
// result, those ciphers will not appear in "My Vault" for the org
|
// result, those ciphers will not appear in "My Vault" for the org
|
||||||
// owner/admin, but they can still be accessed via the org vault view.
|
// owner/admin, but they can still be accessed via the org vault view.
|
||||||
pub async fn find_by_user(user_uuid: &str, visible_only: bool, conn: &mut DbConn) -> Vec<Self> {
|
pub async fn find_by_user(user_uuid: &str, visible_only: bool, conn: &mut DbConn) -> Vec<Self> {
|
||||||
db_run! {conn: {
|
if CONFIG.org_groups_enabled() {
|
||||||
let mut query = ciphers::table
|
db_run! {conn: {
|
||||||
.left_join(ciphers_collections::table.on(
|
let mut query = ciphers::table
|
||||||
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
.left_join(ciphers_collections::table.on(
|
||||||
))
|
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
||||||
.left_join(users_organizations::table.on(
|
))
|
||||||
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())
|
.left_join(users_organizations::table.on(
|
||||||
.and(users_organizations::user_uuid.eq(user_uuid))
|
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())
|
||||||
.and(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
.and(users_organizations::user_uuid.eq(user_uuid))
|
||||||
))
|
.and(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
||||||
.left_join(users_collections::table.on(
|
))
|
||||||
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
.left_join(users_collections::table.on(
|
||||||
// Ensure that users_collections::user_uuid is NULL for unconfirmed users.
|
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
||||||
.and(users_organizations::user_uuid.eq(users_collections::user_uuid))
|
// Ensure that users_collections::user_uuid is NULL for unconfirmed users.
|
||||||
))
|
.and(users_organizations::user_uuid.eq(users_collections::user_uuid))
|
||||||
.left_join(groups_users::table.on(
|
))
|
||||||
groups_users::users_organizations_uuid.eq(users_organizations::uuid)
|
.left_join(groups_users::table.on(
|
||||||
))
|
groups_users::users_organizations_uuid.eq(users_organizations::uuid)
|
||||||
.left_join(groups::table.on(
|
))
|
||||||
groups::uuid.eq(groups_users::groups_uuid)
|
.left_join(groups::table.on(
|
||||||
))
|
groups::uuid.eq(groups_users::groups_uuid)
|
||||||
.left_join(collections_groups::table.on(
|
))
|
||||||
collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid).and(
|
.left_join(collections_groups::table.on(
|
||||||
collections_groups::groups_uuid.eq(groups::uuid)
|
collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid).and(
|
||||||
)
|
collections_groups::groups_uuid.eq(groups::uuid)
|
||||||
))
|
)
|
||||||
.filter(ciphers::user_uuid.eq(user_uuid)) // Cipher owner
|
))
|
||||||
.or_filter(users_organizations::access_all.eq(true)) // access_all in org
|
.filter(ciphers::user_uuid.eq(user_uuid)) // Cipher owner
|
||||||
.or_filter(users_collections::user_uuid.eq(user_uuid)) // Access to collection
|
.or_filter(users_organizations::access_all.eq(true)) // access_all in org
|
||||||
.or_filter(groups::access_all.eq(true)) // Access via groups
|
.or_filter(users_collections::user_uuid.eq(user_uuid)) // Access to collection
|
||||||
.or_filter(collections_groups::collections_uuid.is_not_null()) // Access via groups
|
.or_filter(groups::access_all.eq(true)) // Access via groups
|
||||||
.into_boxed();
|
.or_filter(collections_groups::collections_uuid.is_not_null()) // Access via groups
|
||||||
|
.into_boxed();
|
||||||
|
|
||||||
if !visible_only {
|
if !visible_only {
|
||||||
query = query.or_filter(
|
query = query.or_filter(
|
||||||
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin/owner
|
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin/owner
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
query
|
query
|
||||||
.select(ciphers::all_columns)
|
.select(ciphers::all_columns)
|
||||||
.distinct()
|
.distinct()
|
||||||
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||||
}}
|
}}
|
||||||
|
} else {
|
||||||
|
db_run! {conn: {
|
||||||
|
let mut query = ciphers::table
|
||||||
|
.left_join(ciphers_collections::table.on(
|
||||||
|
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
||||||
|
))
|
||||||
|
.left_join(users_organizations::table.on(
|
||||||
|
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())
|
||||||
|
.and(users_organizations::user_uuid.eq(user_uuid))
|
||||||
|
.and(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
||||||
|
))
|
||||||
|
.left_join(users_collections::table.on(
|
||||||
|
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
||||||
|
// Ensure that users_collections::user_uuid is NULL for unconfirmed users.
|
||||||
|
.and(users_organizations::user_uuid.eq(users_collections::user_uuid))
|
||||||
|
))
|
||||||
|
.filter(ciphers::user_uuid.eq(user_uuid)) // Cipher owner
|
||||||
|
.or_filter(users_organizations::access_all.eq(true)) // access_all in org
|
||||||
|
.or_filter(users_collections::user_uuid.eq(user_uuid)) // Access to collection
|
||||||
|
.into_boxed();
|
||||||
|
|
||||||
|
if !visible_only {
|
||||||
|
query = query.or_filter(
|
||||||
|
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin/owner
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
query
|
||||||
|
.select(ciphers::all_columns)
|
||||||
|
.distinct()
|
||||||
|
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all ciphers visible to the specified user.
|
// Find all ciphers visible to the specified user.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::{CollectionGroup, User, UserOrgStatus, UserOrgType, UserOrganization};
|
use super::{CollectionGroup, User, UserOrgStatus, UserOrgType, UserOrganization};
|
||||||
|
use crate::CONFIG;
|
||||||
|
|
||||||
db_object! {
|
db_object! {
|
||||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||||
|
@ -181,47 +182,74 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_user_uuid(user_uuid: String, conn: &mut DbConn) -> Vec<Self> {
|
pub async fn find_by_user_uuid(user_uuid: String, conn: &mut DbConn) -> Vec<Self> {
|
||||||
db_run! { conn: {
|
if CONFIG.org_groups_enabled() {
|
||||||
collections::table
|
db_run! { conn: {
|
||||||
.left_join(users_collections::table.on(
|
collections::table
|
||||||
users_collections::collection_uuid.eq(collections::uuid).and(
|
.left_join(users_collections::table.on(
|
||||||
users_collections::user_uuid.eq(user_uuid.clone())
|
users_collections::collection_uuid.eq(collections::uuid).and(
|
||||||
|
users_collections::user_uuid.eq(user_uuid.clone())
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.left_join(users_organizations::table.on(
|
||||||
|
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
||||||
|
users_organizations::user_uuid.eq(user_uuid.clone())
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.left_join(groups_users::table.on(
|
||||||
|
groups_users::users_organizations_uuid.eq(users_organizations::uuid)
|
||||||
|
))
|
||||||
|
.left_join(groups::table.on(
|
||||||
|
groups::uuid.eq(groups_users::groups_uuid)
|
||||||
|
))
|
||||||
|
.left_join(collections_groups::table.on(
|
||||||
|
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
||||||
|
collections_groups::collections_uuid.eq(collections::uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.filter(
|
||||||
|
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
||||||
)
|
)
|
||||||
))
|
.filter(
|
||||||
.left_join(users_organizations::table.on(
|
users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection
|
||||||
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
users_organizations::access_all.eq(true) // access_all in Organization
|
||||||
users_organizations::user_uuid.eq(user_uuid.clone())
|
).or(
|
||||||
)
|
groups::access_all.eq(true) // access_all in groups
|
||||||
))
|
).or( // access via groups
|
||||||
.left_join(groups_users::table.on(
|
groups_users::users_organizations_uuid.eq(users_organizations::uuid).and(
|
||||||
groups_users::users_organizations_uuid.eq(users_organizations::uuid)
|
collections_groups::collections_uuid.is_not_null()
|
||||||
))
|
)
|
||||||
.left_join(groups::table.on(
|
|
||||||
groups::uuid.eq(groups_users::groups_uuid)
|
|
||||||
))
|
|
||||||
.left_join(collections_groups::table.on(
|
|
||||||
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
|
||||||
collections_groups::collections_uuid.eq(collections::uuid)
|
|
||||||
)
|
|
||||||
))
|
|
||||||
.filter(
|
|
||||||
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection
|
|
||||||
users_organizations::access_all.eq(true) // access_all in Organization
|
|
||||||
).or(
|
|
||||||
groups::access_all.eq(true) // access_all in groups
|
|
||||||
).or( // access via groups
|
|
||||||
groups_users::users_organizations_uuid.eq(users_organizations::uuid).and(
|
|
||||||
collections_groups::collections_uuid.is_not_null()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
.select(collections::all_columns)
|
||||||
.select(collections::all_columns)
|
.distinct()
|
||||||
.distinct()
|
.load::<CollectionDb>(conn).expect("Error loading collections").from_db()
|
||||||
.load::<CollectionDb>(conn).expect("Error loading collections").from_db()
|
}}
|
||||||
}}
|
} else {
|
||||||
|
db_run! { conn: {
|
||||||
|
collections::table
|
||||||
|
.left_join(users_collections::table.on(
|
||||||
|
users_collections::collection_uuid.eq(collections::uuid).and(
|
||||||
|
users_collections::user_uuid.eq(user_uuid.clone())
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.left_join(users_organizations::table.on(
|
||||||
|
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
||||||
|
users_organizations::user_uuid.eq(user_uuid.clone())
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.filter(
|
||||||
|
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection
|
||||||
|
users_organizations::access_all.eq(true) // access_all in Organization
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.select(collections::all_columns)
|
||||||
|
.distinct()
|
||||||
|
.load::<CollectionDb>(conn).expect("Error loading collections").from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a user has access to a specific collection
|
// Check if a user has access to a specific collection
|
||||||
|
@ -277,45 +305,70 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: String, conn: &mut DbConn) -> Option<Self> {
|
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: String, conn: &mut DbConn) -> Option<Self> {
|
||||||
db_run! { conn: {
|
if CONFIG.org_groups_enabled() {
|
||||||
collections::table
|
db_run! { conn: {
|
||||||
.left_join(users_collections::table.on(
|
collections::table
|
||||||
users_collections::collection_uuid.eq(collections::uuid).and(
|
.left_join(users_collections::table.on(
|
||||||
users_collections::user_uuid.eq(user_uuid.clone())
|
users_collections::collection_uuid.eq(collections::uuid).and(
|
||||||
)
|
users_collections::user_uuid.eq(user_uuid.clone())
|
||||||
))
|
|
||||||
.left_join(users_organizations::table.on(
|
|
||||||
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
|
||||||
users_organizations::user_uuid.eq(user_uuid)
|
|
||||||
)
|
|
||||||
))
|
|
||||||
.left_join(groups_users::table.on(
|
|
||||||
groups_users::users_organizations_uuid.eq(users_organizations::uuid)
|
|
||||||
))
|
|
||||||
.left_join(groups::table.on(
|
|
||||||
groups::uuid.eq(groups_users::groups_uuid)
|
|
||||||
))
|
|
||||||
.left_join(collections_groups::table.on(
|
|
||||||
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
|
||||||
collections_groups::collections_uuid.eq(collections::uuid)
|
|
||||||
)
|
|
||||||
))
|
|
||||||
.filter(collections::uuid.eq(uuid))
|
|
||||||
.filter(
|
|
||||||
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
|
|
||||||
users_organizations::access_all.eq(true).or( // access_all in Organization
|
|
||||||
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
|
|
||||||
)).or(
|
|
||||||
groups::access_all.eq(true) // access_all in groups
|
|
||||||
).or( // access via groups
|
|
||||||
groups_users::users_organizations_uuid.eq(users_organizations::uuid).and(
|
|
||||||
collections_groups::collections_uuid.is_not_null()
|
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
).select(collections::all_columns)
|
.left_join(users_organizations::table.on(
|
||||||
.first::<CollectionDb>(conn).ok()
|
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
||||||
.from_db()
|
users_organizations::user_uuid.eq(user_uuid)
|
||||||
}}
|
)
|
||||||
|
))
|
||||||
|
.left_join(groups_users::table.on(
|
||||||
|
groups_users::users_organizations_uuid.eq(users_organizations::uuid)
|
||||||
|
))
|
||||||
|
.left_join(groups::table.on(
|
||||||
|
groups::uuid.eq(groups_users::groups_uuid)
|
||||||
|
))
|
||||||
|
.left_join(collections_groups::table.on(
|
||||||
|
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
||||||
|
collections_groups::collections_uuid.eq(collections::uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.filter(collections::uuid.eq(uuid))
|
||||||
|
.filter(
|
||||||
|
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
|
||||||
|
users_organizations::access_all.eq(true).or( // access_all in Organization
|
||||||
|
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
|
||||||
|
)).or(
|
||||||
|
groups::access_all.eq(true) // access_all in groups
|
||||||
|
).or( // access via groups
|
||||||
|
groups_users::users_organizations_uuid.eq(users_organizations::uuid).and(
|
||||||
|
collections_groups::collections_uuid.is_not_null()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).select(collections::all_columns)
|
||||||
|
.first::<CollectionDb>(conn).ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
|
} else {
|
||||||
|
db_run! { conn: {
|
||||||
|
collections::table
|
||||||
|
.left_join(users_collections::table.on(
|
||||||
|
users_collections::collection_uuid.eq(collections::uuid).and(
|
||||||
|
users_collections::user_uuid.eq(user_uuid.clone())
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.left_join(users_organizations::table.on(
|
||||||
|
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
||||||
|
users_organizations::user_uuid.eq(user_uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.filter(collections::uuid.eq(uuid))
|
||||||
|
.filter(
|
||||||
|
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
|
||||||
|
users_organizations::access_all.eq(true).or( // access_all in Organization
|
||||||
|
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
|
||||||
|
))
|
||||||
|
).select(collections::all_columns)
|
||||||
|
.first::<CollectionDb>(conn).ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool {
|
pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool {
|
||||||
|
|
Loading…
Reference in a new issue