From 4c3b328aca359ebf6211f53509c15b6c94d70e3f Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Tue, 1 Sep 2020 00:49:48 -0700 Subject: [PATCH] Hide ciphers from non-selected collections for org owners/admins If org owners/admins set their org access to only include selected collections, then ciphers from non-selected collections shouldn't appear in "My Vault". This matches the upstream behavior. --- src/api/core/ciphers.rs | 4 +-- src/db/models/cipher.rs | 59 ++++++++++++++++++++++++------------- src/db/models/collection.rs | 27 ++++++++--------- 3 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 53760ae3..7fc02700 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -82,7 +82,7 @@ fn sync(data: Form, headers: Headers, conn: DbConn) -> JsonResult { let policies = OrgPolicy::find_by_user(&headers.user.uuid, &conn); let policies_json: Vec = policies.iter().map(OrgPolicy::to_json).collect(); - let ciphers = Cipher::find_by_user(&headers.user.uuid, &conn); + let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn); let ciphers_json: Vec = ciphers .iter() .map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)) @@ -107,7 +107,7 @@ fn sync(data: Form, headers: Headers, conn: DbConn) -> JsonResult { #[get("/ciphers")] fn get_ciphers(headers: Headers, conn: DbConn) -> JsonResult { - let ciphers = Cipher::find_by_user(&headers.user.uuid, &conn); + let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn); let ciphers_json: Vec = ciphers .iter() diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index 5fdd98fa..32d8a272 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -382,39 +382,58 @@ impl Cipher { }} } - // Find all ciphers accessible to user - pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec { + // 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. + // + // "Visible" usually means the same as accessible, except when an org + // owner/admin sets their account 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 + // 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. + pub fn find_by_user(user_uuid: &str, visible_only: bool, conn: &DbConn) -> Vec { db_run! {conn: { - ciphers::table - .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) - ) - ) - )) + 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).or( // Cipher owner - users_organizations::access_all.eq(true).or( // access_all in Organization - users_organizations::atype.le(UserOrgType::Admin as i32).or( // Org admin or owner - users_collections::user_uuid.eq(user_uuid).and( // Access to Collection - users_organizations::status.eq(UserOrgStatus::Confirmed as i32) - ) - ) - ) - )) + .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::(conn).expect("Error loading ciphers").from_db() }} } - // Find all ciphers directly owned by user + // Find all ciphers visible to the specified user. + pub fn find_by_user_visible(user_uuid: &str, conn: &DbConn) -> Vec { + Self::find_by_user(user_uuid, true, conn) + } + + // Find all ciphers directly owned by the specified user. pub fn find_owned_by_user(user_uuid: &str, conn: &DbConn) -> Vec { db_run! {conn: { ciphers::table diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs index 18d1ff03..c773b9bf 100644 --- a/src/db/models/collection.rs +++ b/src/db/models/collection.rs @@ -208,21 +208,20 @@ impl Collection { match UserOrganization::find_by_user_and_org(&user_uuid, &self.org_uuid, &conn) { None => false, // Not in Org Some(user_org) => { - if user_org.access_all { - true - } else { - db_run! { conn: { - users_collections::table - .inner_join(collections::table) - .filter(users_collections::collection_uuid.eq(&self.uuid)) - .filter(users_collections::user_uuid.eq(&user_uuid)) - .filter(users_collections::read_only.eq(false)) - .select(collections::all_columns) - .first::(conn) - .ok() - .is_some() // Read only or no access to collection - }} + if user_org.has_full_access() { + return true; } + + db_run! { conn: { + users_collections::table + .filter(users_collections::collection_uuid.eq(&self.uuid)) + .filter(users_collections::user_uuid.eq(user_uuid)) + .filter(users_collections::read_only.eq(false)) + .count() + .first::(conn) + .ok() + .unwrap_or(0) != 0 + }} } } }