From 064a1e368ef4ea9b608d138fb9a12a64555702fc Mon Sep 17 00:00:00 2001 From: MFijak Date: Mon, 1 Aug 2022 16:57:17 +0200 Subject: [PATCH] cipher readonly, hide_passwords --- src/api/core/ciphers.rs | 7 ++++ src/db/models/cipher.rs | 81 +++++++++++++++++++++++++++++++++++------ src/db/models/group.rs | 51 ++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 11 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index b491424e..3fee628d 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -1497,6 +1497,8 @@ pub struct CipherSyncData { pub cipher_collections: HashMap>, pub user_organizations: HashMap, pub user_collections: HashMap, + pub user_collections_groups: Vec, + pub user_groups: Vec, } pub enum CipherSyncType { @@ -1552,6 +1554,9 @@ impl CipherSyncData { .collect() .await; + let user_collections_groups = CollectionGroup::find_by_user(user_uuid, conn).await; + let user_groups = Group::find_by_user(user_uuid, conn).await; + Self { cipher_attachments, cipher_folders, @@ -1559,6 +1564,8 @@ impl CipherSyncData { cipher_collections, user_organizations, user_collections, + user_collections_groups, + user_groups } } } diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index d5f78fbe..b43c4d4e 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -2,7 +2,7 @@ use crate::CONFIG; use chrono::{Duration, NaiveDateTime, Utc}; use serde_json::Value; -use super::{Attachment, CollectionCipher, Favorite, FolderCipher, User, UserOrgStatus, UserOrgType, UserOrganization}; +use super::{Attachment, CollectionCipher, Favorite, FolderCipher, User, UserOrgStatus, UserOrgType, UserOrganization, Group}; use crate::api::core::CipherSyncData; @@ -368,7 +368,7 @@ impl Cipher { // Check whether this cipher is directly owned by the user, or is in // a collection that the user has full access to. If so, there are no // access restrictions. - if self.is_owned_by_user(user_uuid) || self.is_in_full_access_org(user_uuid, cipher_sync_data, conn).await { + if self.is_owned_by_user(user_uuid) || self.is_in_full_access_org(user_uuid, cipher_sync_data, conn).await || Group::is_in_full_access_group(user_uuid, conn).await { return Some((false, false)); } @@ -376,14 +376,30 @@ impl Cipher { let mut rows: Vec<(bool, bool)> = Vec::new(); if let Some(collections) = cipher_sync_data.cipher_collections.get(&self.uuid) { for collection in collections { + //User permissions if let Some(uc) = cipher_sync_data.user_collections.get(collection) { rows.push((uc.read_only, uc.hide_passwords)); } + + //Group permissions + if let Some(gc) = cipher_sync_data.user_collections_groups + .iter() + .find(|collection_group| collection_group.collections_uuid == collection.clone()) { + rows.push((gc.read_only, gc.hide_passwords)); + } } } rows } else { - self.get_collections_access_flags(user_uuid, conn).await + let mut user_collections_access_flags = self.get_user_collections_access_flags(user_uuid, conn).await; + let mut group_collections_access_flags = self.get_group_collections_access_flags(user_uuid, conn).await; + + let mut result = Vec::new(); + + result.append(&mut user_collections_access_flags); + result.append(&mut group_collections_access_flags); + + result }; if rows.is_empty() { @@ -410,7 +426,7 @@ impl Cipher { Some((read_only, hide_passwords)) } - pub async fn get_collections_access_flags(&self, user_uuid: &str, conn: &DbConn) -> Vec<(bool, bool)> { + async fn get_user_collections_access_flags(&self, user_uuid: &str, conn: &DbConn) -> Vec<(bool, bool)> { db_run! {conn: { // Check whether this cipher is in any collections accessible to the // user. If so, retrieve the access flags for each collection. @@ -423,7 +439,30 @@ impl Cipher { .and(users_collections::user_uuid.eq(user_uuid)))) .select((users_collections::read_only, users_collections::hide_passwords)) .load::<(bool, bool)>(conn) - .expect("Error getting access restrictions") + .expect("Error getting user access restrictions") + }} + } + + async fn get_group_collections_access_flags(&self, user_uuid: &str, conn: &DbConn) -> Vec<(bool, bool)> { + db_run! {conn: { + ciphers::table + .filter(ciphers::uuid.eq(&self.uuid)) + .inner_join(ciphers_collections::table.on( + ciphers::uuid.eq(ciphers_collections::cipher_uuid) + )) + .inner_join(collection_groups::table.on( + collection_groups::collections_uuid.eq(ciphers_collections::collection_uuid) + )) + .inner_join(groups_users::table.on( + groups_users::groups_uuid.eq(collection_groups::groups_uuid) + )) + .inner_join(users_organizations::table.on( + users_organizations::uuid.eq(groups_users::users_organizations_uuid) + )) + .filter(users_organizations::user_uuid.eq(user_uuid)) + .select((collection_groups::read_only, collection_groups::hide_passwords)) + .load::<(bool, bool)>(conn) + .expect("Error getting group access restrictions") }} } @@ -476,10 +515,10 @@ impl Cipher { // Find all ciphers accessible or visible to the specified user. // // "Accessible" means the user has read access to the cipher, either via - // direct ownership or via collection access. + // direct ownership, collection or via group access. // // "Visible" usually means the same as accessible, except when an org - // owner/admin sets their account to have access to only selected + // owner/admin sets their account or group to have access to only selected // collections in the org (presumably because they aren't interested in // the other collections in the org). In this case, if `visible_only` is // true, then the non-interesting ciphers will not be returned. As a @@ -501,9 +540,20 @@ impl Cipher { // 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::table.on( + groups::uuid.eq(groups_users::groups_uuid) + )) + .left_join(collection_groups::table.on( + collection_groups::collections_uuid.eq(ciphers_collections::collection_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 + .or_filter(groups::access_all.eq(true)) // Access via groups + .or_filter(collection_groups::collections_uuid.is_not_null()) // Access via groups .into_boxed(); if !visible_only { @@ -629,11 +679,20 @@ impl Cipher { users_collections::user_uuid.eq(user_id) ) )) - .filter(users_collections::user_uuid.eq(user_id).or( // User has access to collection - users_organizations::access_all.eq(true).or( // User has access all - users_organizations::atype.le(UserOrgType::Admin as i32) // User is admin or owner - ) + .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(collection_groups::table.on( + collection_groups::collections_uuid.eq(ciphers_collections::collection_uuid) )) + .or_filter(users_collections::user_uuid.eq(user_id)) // User has access to collection + .or_filter(users_organizations::access_all.eq(true)) // User has access all + .or_filter(users_organizations::atype.le(UserOrgType::Admin as i32)) // User is admin or owner + .or_filter(groups::access_all.eq(true)) //Access via group + .or_filter(collection_groups::collections_uuid.is_not_null()) //Access via group .select(ciphers_collections::all_columns) .load::<(String, String)>(conn).unwrap_or_default() }} diff --git a/src/db/models/group.rs b/src/db/models/group.rs index a9ed9ad9..6ebcbd2d 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -148,6 +148,40 @@ impl Group { }} } + pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec { + db_run! { conn: { + groups::table + .inner_join(groups_users::table.on( + groups_users::groups_uuid.eq(groups::uuid) + )) + .inner_join(users_organizations::table.on( + users_organizations::uuid.eq(groups_users::users_organizations_uuid) + )) + .filter(users_organizations::user_uuid.eq(user_uuid)) + .select(groups::all_columns) + .load::(conn) + .expect("Error loading user groups") + .from_db() + }} + } + + pub async fn is_in_full_access_group(user_uuid: &str, conn: &DbConn) -> bool { + db_run! { conn: { + groups::table + .inner_join(groups_users::table.on( + groups_users::groups_uuid.eq(groups::uuid) + )) + .inner_join(users_organizations::table.on( + users_organizations::uuid.eq(groups_users::users_organizations_uuid) + )) + .filter(users_organizations::user_uuid.eq(user_uuid)) + .filter(groups::access_all.eq(true)) + .select(groups::access_all) + .first::(conn) + .unwrap_or_default() + }} + } + pub async fn delete(&self, conn: &DbConn) -> EmptyResult { CollectionGroup::delete_all_by_group(&self.uuid, &conn).await?; GroupUser::delete_all_by_group(&self.uuid, &conn).await?; @@ -243,6 +277,23 @@ impl CollectionGroup { }} } + pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec { + db_run! { conn: { + collection_groups::table + .inner_join(groups_users::table.on( + groups_users::groups_uuid.eq(collection_groups::groups_uuid) + )) + .inner_join(users_organizations::table.on( + users_organizations::uuid.eq(groups_users::users_organizations_uuid) + )) + .filter(users_organizations::user_uuid.eq(user_uuid)) + .select(collection_groups::all_columns) + .load::(conn) + .expect("Error loading user collection groups") + .from_db() + }} + } + pub async fn delete(&self, conn: &DbConn) -> EmptyResult { let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await; for group_user in group_users {