1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2025-01-22 07:09:00 +01:00

Add manage role for collections and groups (#5386)

* Add manage role for collections and groups

This commit will add the manage role/column to collections and groups.
We need this to allow users part of a collection either directly or via groups to be able to delete ciphers.
Without this, they are only able to either edit or view them when using new clients, since these check the manage role.

Still trying to keep it compatible with previous versions and able to revert to an older Vaultwarden version and the `access_all` feature of the older installations.
In a future version we should really check and fix these rights and create some kind of migration step to also remove the `access_all` feature and convert that to a `manage` option.
But this commit at least creates the base for this already.

This should resolve #5367

Signed-off-by: BlackDex <black.dex@gmail.com>

* Fix an issue with access_all

If owners or admins do not have the `access_all` flag set, in case they do not want to see all collection on the password manager view, they didn't see any collections at all anymore.

This should fix that they are still able to view all the collections and have access to it.

Signed-off-by: BlackDex <black.dex@gmail.com>

---------

Signed-off-by: BlackDex <black.dex@gmail.com>
Dieser Commit ist enthalten in:
Mathijs van Veluw 2025-01-21 23:33:41 +01:00 committet von GitHub
Ursprung ef2695de0c
Commit d1dee04615
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
16 geänderte Dateien mit 184 neuen und 70 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,5 @@
ALTER TABLE users_collections
ADD COLUMN manage BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE collections_groups
ADD COLUMN manage BOOLEAN NOT NULL DEFAULT FALSE;

Datei anzeigen

@ -0,0 +1,5 @@
ALTER TABLE users_collections
ADD COLUMN manage BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE collections_groups
ADD COLUMN manage BOOLEAN NOT NULL DEFAULT FALSE;

Datei anzeigen

@ -0,0 +1,5 @@
ALTER TABLE users_collections
ADD COLUMN manage BOOLEAN NOT NULL DEFAULT 0; -- FALSE
ALTER TABLE collections_groups
ADD COLUMN manage BOOLEAN NOT NULL DEFAULT 0; -- FALSE

Datei anzeigen

@ -140,6 +140,7 @@ struct NewCollectionGroupData {
hide_passwords: bool, hide_passwords: bool,
id: GroupId, id: GroupId,
read_only: bool, read_only: bool,
manage: bool,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -148,6 +149,7 @@ struct NewCollectionMemberData {
hide_passwords: bool, hide_passwords: bool,
id: MembershipId, id: MembershipId,
read_only: bool, read_only: bool,
manage: bool,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -362,18 +364,13 @@ async fn get_org_collections_details(
|| (CONFIG.org_groups_enabled() || (CONFIG.org_groups_enabled()
&& GroupUser::has_access_to_collection_by_member(&col.uuid, &member.uuid, &mut conn).await); && GroupUser::has_access_to_collection_by_member(&col.uuid, &member.uuid, &mut conn).await);
// Not assigned collections should not be returned
if !assigned {
continue;
}
// get the users assigned directly to the given collection // get the users assigned directly to the given collection
let users: Vec<Value> = col_users let users: Vec<Value> = col_users
.iter() .iter()
.filter(|collection_user| collection_user.collection_uuid == col.uuid) .filter(|collection_member| collection_member.collection_uuid == col.uuid)
.map(|collection_user| { .map(|collection_member| {
collection_user.to_json_details_for_user( collection_member.to_json_details_for_member(
*membership_type.get(&collection_user.membership_uuid).unwrap_or(&(MembershipType::User as i32)), *membership_type.get(&collection_member.membership_uuid).unwrap_or(&(MembershipType::User as i32)),
) )
}) })
.collect(); .collect();
@ -437,7 +434,7 @@ async fn post_organization_collections(
.await; .await;
for group in data.groups { for group in data.groups {
CollectionGroup::new(collection.uuid.clone(), group.id, group.read_only, group.hide_passwords) CollectionGroup::new(collection.uuid.clone(), group.id, group.read_only, group.hide_passwords, group.manage)
.save(&mut conn) .save(&mut conn)
.await?; .await?;
} }
@ -451,12 +448,19 @@ async fn post_organization_collections(
continue; continue;
} }
CollectionUser::save(&member.user_uuid, &collection.uuid, user.read_only, user.hide_passwords, &mut conn) CollectionUser::save(
.await?; &member.user_uuid,
&collection.uuid,
user.read_only,
user.hide_passwords,
user.manage,
&mut conn,
)
.await?;
} }
if headers.membership.atype == MembershipType::Manager && !headers.membership.access_all { if headers.membership.atype == MembershipType::Manager && !headers.membership.access_all {
CollectionUser::save(&headers.membership.user_uuid, &collection.uuid, false, false, &mut conn).await?; CollectionUser::save(&headers.membership.user_uuid, &collection.uuid, false, false, false, &mut conn).await?;
} }
Ok(Json(collection.to_json())) Ok(Json(collection.to_json()))
@ -513,7 +517,9 @@ async fn post_organization_collection_update(
CollectionGroup::delete_all_by_collection(&col_id, &mut conn).await?; CollectionGroup::delete_all_by_collection(&col_id, &mut conn).await?;
for group in data.groups { for group in data.groups {
CollectionGroup::new(col_id.clone(), group.id, group.read_only, group.hide_passwords).save(&mut conn).await?; CollectionGroup::new(col_id.clone(), group.id, group.read_only, group.hide_passwords, group.manage)
.save(&mut conn)
.await?;
} }
CollectionUser::delete_all_by_collection(&col_id, &mut conn).await?; CollectionUser::delete_all_by_collection(&col_id, &mut conn).await?;
@ -527,7 +533,8 @@ async fn post_organization_collection_update(
continue; continue;
} }
CollectionUser::save(&member.user_uuid, &col_id, user.read_only, user.hide_passwords, &mut conn).await?; CollectionUser::save(&member.user_uuid, &col_id, user.read_only, user.hide_passwords, user.manage, &mut conn)
.await?;
} }
Ok(Json(collection.to_json_details(&headers.user.uuid, None, &mut conn).await)) Ok(Json(collection.to_json_details(&headers.user.uuid, None, &mut conn).await))
@ -685,10 +692,10 @@ async fn get_org_collection_detail(
CollectionUser::find_by_collection_swap_user_uuid_with_member_uuid(&collection.uuid, &mut conn) CollectionUser::find_by_collection_swap_user_uuid_with_member_uuid(&collection.uuid, &mut conn)
.await .await
.iter() .iter()
.map(|collection_user| { .map(|collection_member| {
collection_user.to_json_details_for_user( collection_member.to_json_details_for_member(
*membership_type *membership_type
.get(&collection_user.membership_uuid) .get(&collection_member.membership_uuid)
.unwrap_or(&(MembershipType::User as i32)), .unwrap_or(&(MembershipType::User as i32)),
) )
}) })
@ -758,7 +765,7 @@ async fn put_collection_users(
continue; continue;
} }
CollectionUser::save(&user.user_uuid, &col_id, d.read_only, d.hide_passwords, &mut conn).await?; CollectionUser::save(&user.user_uuid, &col_id, d.read_only, d.hide_passwords, d.manage, &mut conn).await?;
} }
Ok(()) Ok(())
@ -866,6 +873,7 @@ struct CollectionData {
id: CollectionId, id: CollectionId,
read_only: bool, read_only: bool,
hide_passwords: bool, hide_passwords: bool,
manage: bool,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -874,6 +882,7 @@ struct MembershipData {
id: MembershipId, id: MembershipId,
read_only: bool, read_only: bool,
hide_passwords: bool, hide_passwords: bool,
manage: bool,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -1012,6 +1021,7 @@ async fn send_invite(
&collection.uuid, &collection.uuid,
col.read_only, col.read_only,
col.hide_passwords, col.hide_passwords,
col.manage,
&mut conn, &mut conn,
) )
.await?; .await?;
@ -1504,6 +1514,7 @@ async fn edit_member(
&collection.uuid, &collection.uuid,
col.read_only, col.read_only,
col.hide_passwords, col.hide_passwords,
col.manage,
&mut conn, &mut conn,
) )
.await?; .await?;
@ -2475,11 +2486,12 @@ struct SelectedCollection {
id: CollectionId, id: CollectionId,
read_only: bool, read_only: bool,
hide_passwords: bool, hide_passwords: bool,
manage: bool,
} }
impl SelectedCollection { impl SelectedCollection {
pub fn to_collection_group(&self, groups_uuid: GroupId) -> CollectionGroup { pub fn to_collection_group(&self, groups_uuid: GroupId) -> CollectionGroup {
CollectionGroup::new(self.id.clone(), groups_uuid, self.read_only, self.hide_passwords) CollectionGroup::new(self.id.clone(), groups_uuid, self.read_only, self.hide_passwords, self.manage)
} }
} }

Datei anzeigen

@ -291,9 +291,7 @@ fn get_favicons_node(dom: Tokenizer<StringReader<'_>, FaviconEmitter>, icons: &m
TAG_HEAD if token.closing => { TAG_HEAD if token.closing => {
break; break;
} }
_ => { _ => {}
continue;
}
} }
} }

Datei anzeigen

@ -157,7 +157,6 @@ fn websockets_hub<'r>(
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) { if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
yield Message::binary(INITIAL_RESPONSE); yield Message::binary(INITIAL_RESPONSE);
continue;
} }
} }
@ -225,7 +224,6 @@ fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> R
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) { if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
yield Message::binary(INITIAL_RESPONSE); yield Message::binary(INITIAL_RESPONSE);
continue;
} }
} }

Datei anzeigen

@ -158,16 +158,16 @@ impl Cipher {
// We don't need these values at all for Organizational syncs // We don't need these values at all for Organizational syncs
// Skip any other database calls if this is the case and just return false. // Skip any other database calls if this is the case and just return false.
let (read_only, hide_passwords) = if sync_type == CipherSyncType::User { let (read_only, hide_passwords, _) = if sync_type == CipherSyncType::User {
match self.get_access_restrictions(user_uuid, cipher_sync_data, conn).await { match self.get_access_restrictions(user_uuid, cipher_sync_data, conn).await {
Some((ro, hp)) => (ro, hp), Some((ro, hp, mn)) => (ro, hp, mn),
None => { None => {
error!("Cipher ownership assertion failure"); error!("Cipher ownership assertion failure");
(true, true) (true, true, false)
} }
} }
} else { } else {
(false, false) (false, false, false)
}; };
let fields_json: Vec<_> = self let fields_json: Vec<_> = self
@ -567,14 +567,14 @@ impl Cipher {
/// Returns the user's access restrictions to this cipher. A return value /// Returns the user's access restrictions to this cipher. A return value
/// of None means that this cipher does not belong to the user, and is /// of None means that this cipher does not belong to the user, and is
/// not in any collection the user has access to. Otherwise, the user has /// not in any collection the user has access to. Otherwise, the user has
/// access to this cipher, and Some(read_only, hide_passwords) represents /// access to this cipher, and Some(read_only, hide_passwords, manage) represents
/// the access restrictions. /// the access restrictions.
pub async fn get_access_restrictions( pub async fn get_access_restrictions(
&self, &self,
user_uuid: &UserId, user_uuid: &UserId,
cipher_sync_data: Option<&CipherSyncData>, cipher_sync_data: Option<&CipherSyncData>,
conn: &mut DbConn, conn: &mut DbConn,
) -> Option<(bool, bool)> { ) -> Option<(bool, bool, bool)> {
// Check whether this cipher is directly owned by the user, or is in // 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 // a collection that the user has full access to. If so, there are no
// access restrictions. // access restrictions.
@ -582,21 +582,21 @@ impl Cipher {
|| self.is_in_full_access_org(user_uuid, cipher_sync_data, conn).await || self.is_in_full_access_org(user_uuid, cipher_sync_data, conn).await
|| self.is_in_full_access_group(user_uuid, cipher_sync_data, conn).await || self.is_in_full_access_group(user_uuid, cipher_sync_data, conn).await
{ {
return Some((false, false)); return Some((false, false, true));
} }
let rows = if let Some(cipher_sync_data) = cipher_sync_data { let rows = if let Some(cipher_sync_data) = cipher_sync_data {
let mut rows: Vec<(bool, bool)> = Vec::new(); let mut rows: Vec<(bool, bool, bool)> = Vec::new();
if let Some(collections) = cipher_sync_data.cipher_collections.get(&self.uuid) { if let Some(collections) = cipher_sync_data.cipher_collections.get(&self.uuid) {
for collection in collections { for collection in collections {
//User permissions //User permissions
if let Some(uc) = cipher_sync_data.user_collections.get(collection) { if let Some(cu) = cipher_sync_data.user_collections.get(collection) {
rows.push((uc.read_only, uc.hide_passwords)); rows.push((cu.read_only, cu.hide_passwords, cu.manage));
} }
//Group permissions //Group permissions
if let Some(cg) = cipher_sync_data.user_collections_groups.get(collection) { if let Some(cg) = cipher_sync_data.user_collections_groups.get(collection) {
rows.push((cg.read_only, cg.hide_passwords)); rows.push((cg.read_only, cg.hide_passwords, cg.manage));
} }
} }
} }
@ -623,15 +623,21 @@ impl Cipher {
// booleans and this behavior isn't portable anyway. // booleans and this behavior isn't portable anyway.
let mut read_only = true; let mut read_only = true;
let mut hide_passwords = true; let mut hide_passwords = true;
for (ro, hp) in rows.iter() { let mut manage = false;
for (ro, hp, mn) in rows.iter() {
read_only &= ro; read_only &= ro;
hide_passwords &= hp; hide_passwords &= hp;
manage &= mn;
} }
Some((read_only, hide_passwords)) Some((read_only, hide_passwords, manage))
} }
async fn get_user_collections_access_flags(&self, user_uuid: &UserId, conn: &mut DbConn) -> Vec<(bool, bool)> { async fn get_user_collections_access_flags(
&self,
user_uuid: &UserId,
conn: &mut DbConn,
) -> Vec<(bool, bool, bool)> {
db_run! {conn: { db_run! {conn: {
// Check whether this cipher is in any collections accessible to the // Check whether this cipher is in any collections accessible to the
// user. If so, retrieve the access flags for each collection. // user. If so, retrieve the access flags for each collection.
@ -642,13 +648,17 @@ impl Cipher {
.inner_join(users_collections::table.on( .inner_join(users_collections::table.on(
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid) ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
.and(users_collections::user_uuid.eq(user_uuid)))) .and(users_collections::user_uuid.eq(user_uuid))))
.select((users_collections::read_only, users_collections::hide_passwords)) .select((users_collections::read_only, users_collections::hide_passwords, users_collections::manage))
.load::<(bool, bool)>(conn) .load::<(bool, bool, bool)>(conn)
.expect("Error getting user access restrictions") .expect("Error getting user access restrictions")
}} }}
} }
async fn get_group_collections_access_flags(&self, user_uuid: &UserId, conn: &mut DbConn) -> Vec<(bool, bool)> { async fn get_group_collections_access_flags(
&self,
user_uuid: &UserId,
conn: &mut DbConn,
) -> Vec<(bool, bool, bool)> {
if !CONFIG.org_groups_enabled() { if !CONFIG.org_groups_enabled() {
return Vec::new(); return Vec::new();
} }
@ -668,15 +678,15 @@ impl Cipher {
users_organizations::uuid.eq(groups_users::users_organizations_uuid) users_organizations::uuid.eq(groups_users::users_organizations_uuid)
)) ))
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.select((collections_groups::read_only, collections_groups::hide_passwords)) .select((collections_groups::read_only, collections_groups::hide_passwords, collections_groups::manage))
.load::<(bool, bool)>(conn) .load::<(bool, bool, bool)>(conn)
.expect("Error getting group access restrictions") .expect("Error getting group access restrictions")
}} }}
} }
pub async fn is_write_accessible_to_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool { pub async fn is_write_accessible_to_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
match self.get_access_restrictions(user_uuid, None, conn).await { match self.get_access_restrictions(user_uuid, None, conn).await {
Some((read_only, _hide_passwords)) => !read_only, Some((read_only, _hide_passwords, manage)) => !read_only || manage,
None => false, None => false,
} }
} }

Datei anzeigen

@ -27,6 +27,7 @@ db_object! {
pub collection_uuid: CollectionId, pub collection_uuid: CollectionId,
pub read_only: bool, pub read_only: bool,
pub hide_passwords: bool, pub hide_passwords: bool,
pub manage: bool,
} }
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
@ -83,18 +84,26 @@ impl Collection {
cipher_sync_data: Option<&crate::api::core::CipherSyncData>, cipher_sync_data: Option<&crate::api::core::CipherSyncData>,
conn: &mut DbConn, conn: &mut DbConn,
) -> Value { ) -> Value {
let (read_only, hide_passwords, can_manage) = if let Some(cipher_sync_data) = cipher_sync_data { let (read_only, hide_passwords, manage) = if let Some(cipher_sync_data) = cipher_sync_data {
match cipher_sync_data.members.get(&self.org_uuid) { match cipher_sync_data.members.get(&self.org_uuid) {
// Only for Manager types Bitwarden returns true for the can_manage option // Only for Manager types Bitwarden returns true for the manage option
// Owners and Admins always have true // Owners and Admins always have true. Users are not able to have full access
Some(m) if m.has_full_access() => (false, false, m.atype >= MembershipType::Manager), Some(m) if m.has_full_access() => (false, false, m.atype >= MembershipType::Manager),
Some(m) => { Some(m) => {
// Only let a manager manage collections when the have full read/write access // Only let a manager manage collections when the have full read/write access
let is_manager = m.atype == MembershipType::Manager; let is_manager = m.atype == MembershipType::Manager;
if let Some(uc) = cipher_sync_data.user_collections.get(&self.uuid) { if let Some(cu) = cipher_sync_data.user_collections.get(&self.uuid) {
(uc.read_only, uc.hide_passwords, is_manager && !uc.read_only && !uc.hide_passwords) (
cu.read_only,
cu.hide_passwords,
cu.manage || (is_manager && !cu.read_only && !cu.hide_passwords),
)
} else if let Some(cg) = cipher_sync_data.user_collections_groups.get(&self.uuid) { } else if let Some(cg) = cipher_sync_data.user_collections_groups.get(&self.uuid) {
(cg.read_only, cg.hide_passwords, is_manager && !cg.read_only && !cg.hide_passwords) (
cg.read_only,
cg.hide_passwords,
cg.manage || (is_manager && !cg.read_only && !cg.hide_passwords),
)
} else { } else {
(false, false, false) (false, false, false)
} }
@ -104,17 +113,14 @@ impl Collection {
} else { } else {
match Membership::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn).await { match Membership::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn).await {
Some(m) if m.has_full_access() => (false, false, m.atype >= MembershipType::Manager), Some(m) if m.has_full_access() => (false, false, m.atype >= MembershipType::Manager),
Some(_) if self.is_manageable_by_user(user_uuid, conn).await => (false, false, true),
Some(m) => { Some(m) => {
let is_manager = m.atype == MembershipType::Manager; let is_manager = m.atype == MembershipType::Manager;
let read_only = !self.is_writable_by_user(user_uuid, conn).await; let read_only = !self.is_writable_by_user(user_uuid, conn).await;
let hide_passwords = self.hide_passwords_for_user(user_uuid, conn).await; let hide_passwords = self.hide_passwords_for_user(user_uuid, conn).await;
(read_only, hide_passwords, is_manager && !read_only && !hide_passwords) (read_only, hide_passwords, is_manager && !read_only && !hide_passwords)
} }
_ => ( _ => (true, true, false),
!self.is_writable_by_user(user_uuid, conn).await,
self.hide_passwords_for_user(user_uuid, conn).await,
false,
),
} }
}; };
@ -122,7 +128,7 @@ impl Collection {
json_object["object"] = json!("collectionDetails"); json_object["object"] = json!("collectionDetails");
json_object["readOnly"] = json!(read_only); json_object["readOnly"] = json!(read_only);
json_object["hidePasswords"] = json!(hide_passwords); json_object["hidePasswords"] = json!(hide_passwords);
json_object["manage"] = json!(can_manage); json_object["manage"] = json!(manage);
json_object json_object
} }
@ -507,6 +513,52 @@ impl Collection {
.unwrap_or(0) != 0 .unwrap_or(0) != 0
}} }}
} }
pub async fn is_manageable_by_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
let user_uuid = user_uuid.to_string();
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)
)
))
.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(&self.uuid))
.filter(
users_collections::collection_uuid.eq(&self.uuid).and(users_collections::manage.eq(true)).or(// Directly accessed collection
users_organizations::access_all.eq(true).or( // access_all in Organization
users_organizations::atype.le(MembershipType::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().and(
collections_groups::manage.eq(true))
)
)
)
.count()
.first::<i64>(conn)
.ok()
.unwrap_or(0) != 0
}}
}
} }
/// Database methods /// Database methods
@ -537,7 +589,7 @@ impl CollectionUser {
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid))) .inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
.filter(collections::org_uuid.eq(org_uuid)) .filter(collections::org_uuid.eq(org_uuid))
.inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid))) .inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid)))
.select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords)) .select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords, users_collections::manage))
.load::<CollectionUserDb>(conn) .load::<CollectionUserDb>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
.from_db() .from_db()
@ -550,6 +602,7 @@ impl CollectionUser {
collection_uuid: &CollectionId, collection_uuid: &CollectionId,
read_only: bool, read_only: bool,
hide_passwords: bool, hide_passwords: bool,
manage: bool,
conn: &mut DbConn, conn: &mut DbConn,
) -> EmptyResult { ) -> EmptyResult {
User::update_uuid_revision(user_uuid, conn).await; User::update_uuid_revision(user_uuid, conn).await;
@ -562,6 +615,7 @@ impl CollectionUser {
users_collections::collection_uuid.eq(collection_uuid), users_collections::collection_uuid.eq(collection_uuid),
users_collections::read_only.eq(read_only), users_collections::read_only.eq(read_only),
users_collections::hide_passwords.eq(hide_passwords), users_collections::hide_passwords.eq(hide_passwords),
users_collections::manage.eq(manage),
)) ))
.execute(conn) .execute(conn)
{ {
@ -576,6 +630,7 @@ impl CollectionUser {
users_collections::collection_uuid.eq(collection_uuid), users_collections::collection_uuid.eq(collection_uuid),
users_collections::read_only.eq(read_only), users_collections::read_only.eq(read_only),
users_collections::hide_passwords.eq(hide_passwords), users_collections::hide_passwords.eq(hide_passwords),
users_collections::manage.eq(manage),
)) ))
.execute(conn) .execute(conn)
.map_res("Error adding user to collection") .map_res("Error adding user to collection")
@ -590,12 +645,14 @@ impl CollectionUser {
users_collections::collection_uuid.eq(collection_uuid), users_collections::collection_uuid.eq(collection_uuid),
users_collections::read_only.eq(read_only), users_collections::read_only.eq(read_only),
users_collections::hide_passwords.eq(hide_passwords), users_collections::hide_passwords.eq(hide_passwords),
users_collections::manage.eq(manage),
)) ))
.on_conflict((users_collections::user_uuid, users_collections::collection_uuid)) .on_conflict((users_collections::user_uuid, users_collections::collection_uuid))
.do_update() .do_update()
.set(( .set((
users_collections::read_only.eq(read_only), users_collections::read_only.eq(read_only),
users_collections::hide_passwords.eq(hide_passwords), users_collections::hide_passwords.eq(hide_passwords),
users_collections::manage.eq(manage),
)) ))
.execute(conn) .execute(conn)
.map_res("Error adding user to collection") .map_res("Error adding user to collection")
@ -636,7 +693,7 @@ impl CollectionUser {
users_collections::table users_collections::table
.filter(users_collections::collection_uuid.eq(collection_uuid)) .filter(users_collections::collection_uuid.eq(collection_uuid))
.inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid))) .inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid)))
.select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords)) .select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords, users_collections::manage))
.load::<CollectionUserDb>(conn) .load::<CollectionUserDb>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
.from_db() .from_db()
@ -787,15 +844,17 @@ pub struct CollectionMembership {
pub collection_uuid: CollectionId, pub collection_uuid: CollectionId,
pub read_only: bool, pub read_only: bool,
pub hide_passwords: bool, pub hide_passwords: bool,
pub manage: bool,
} }
impl CollectionMembership { impl CollectionMembership {
pub fn to_json_details_for_user(&self, membership_type: i32) -> Value { pub fn to_json_details_for_member(&self, membership_type: i32) -> Value {
json!({ json!({
"id": self.membership_uuid, "id": self.membership_uuid,
"readOnly": self.read_only, "readOnly": self.read_only,
"hidePasswords": self.hide_passwords, "hidePasswords": self.hide_passwords,
"manage": membership_type >= MembershipType::Admin "manage": membership_type >= MembershipType::Admin
|| self.manage
|| (membership_type == MembershipType::Manager || (membership_type == MembershipType::Manager
&& !self.read_only && !self.read_only
&& !self.hide_passwords), && !self.hide_passwords),
@ -810,6 +869,7 @@ impl From<CollectionUser> for CollectionMembership {
collection_uuid: c.collection_uuid, collection_uuid: c.collection_uuid,
read_only: c.read_only, read_only: c.read_only,
hide_passwords: c.hide_passwords, hide_passwords: c.hide_passwords,
manage: c.manage,
} }
} }
} }

Datei anzeigen

@ -29,6 +29,7 @@ db_object! {
pub groups_uuid: GroupId, pub groups_uuid: GroupId,
pub read_only: bool, pub read_only: bool,
pub hide_passwords: bool, pub hide_passwords: bool,
pub manage: bool,
} }
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
@ -92,7 +93,7 @@ impl Group {
"id": entry.collections_uuid, "id": entry.collections_uuid,
"readOnly": entry.read_only, "readOnly": entry.read_only,
"hidePasswords": entry.hide_passwords, "hidePasswords": entry.hide_passwords,
"manage": !entry.read_only && !entry.hide_passwords, "manage": entry.manage,
}) })
}) })
.collect(); .collect();
@ -118,12 +119,19 @@ impl Group {
} }
impl CollectionGroup { impl CollectionGroup {
pub fn new(collections_uuid: CollectionId, groups_uuid: GroupId, read_only: bool, hide_passwords: bool) -> Self { pub fn new(
collections_uuid: CollectionId,
groups_uuid: GroupId,
read_only: bool,
hide_passwords: bool,
manage: bool,
) -> Self {
Self { Self {
collections_uuid, collections_uuid,
groups_uuid, groups_uuid,
read_only, read_only,
hide_passwords, hide_passwords,
manage,
} }
} }
@ -131,11 +139,12 @@ impl CollectionGroup {
// If both read_only and hide_passwords are false, then manage should be true // If both read_only and hide_passwords are false, then manage should be true
// You can't have an entry with read_only and manage, or hide_passwords and manage // You can't have an entry with read_only and manage, or hide_passwords and manage
// Or an entry with everything to false // Or an entry with everything to false
// For backwards compaibility and migration proposes we keep checking read_only and hide_password
json!({ json!({
"id": self.groups_uuid, "id": self.groups_uuid,
"readOnly": self.read_only, "readOnly": self.read_only,
"hidePasswords": self.hide_passwords, "hidePasswords": self.hide_passwords,
"manage": !self.read_only && !self.hide_passwords, "manage": self.manage || (!self.read_only && !self.hide_passwords),
}) })
} }
} }
@ -319,6 +328,7 @@ impl CollectionGroup {
collections_groups::groups_uuid.eq(&self.groups_uuid), collections_groups::groups_uuid.eq(&self.groups_uuid),
collections_groups::read_only.eq(&self.read_only), collections_groups::read_only.eq(&self.read_only),
collections_groups::hide_passwords.eq(&self.hide_passwords), collections_groups::hide_passwords.eq(&self.hide_passwords),
collections_groups::manage.eq(&self.manage),
)) ))
.execute(conn) .execute(conn)
{ {
@ -333,6 +343,7 @@ impl CollectionGroup {
collections_groups::groups_uuid.eq(&self.groups_uuid), collections_groups::groups_uuid.eq(&self.groups_uuid),
collections_groups::read_only.eq(&self.read_only), collections_groups::read_only.eq(&self.read_only),
collections_groups::hide_passwords.eq(&self.hide_passwords), collections_groups::hide_passwords.eq(&self.hide_passwords),
collections_groups::manage.eq(&self.manage),
)) ))
.execute(conn) .execute(conn)
.map_res("Error adding group to collection") .map_res("Error adding group to collection")
@ -347,12 +358,14 @@ impl CollectionGroup {
collections_groups::groups_uuid.eq(&self.groups_uuid), collections_groups::groups_uuid.eq(&self.groups_uuid),
collections_groups::read_only.eq(self.read_only), collections_groups::read_only.eq(self.read_only),
collections_groups::hide_passwords.eq(self.hide_passwords), collections_groups::hide_passwords.eq(self.hide_passwords),
collections_groups::manage.eq(self.manage),
)) ))
.on_conflict((collections_groups::collections_uuid, collections_groups::groups_uuid)) .on_conflict((collections_groups::collections_uuid, collections_groups::groups_uuid))
.do_update() .do_update()
.set(( .set((
collections_groups::read_only.eq(self.read_only), collections_groups::read_only.eq(self.read_only),
collections_groups::hide_passwords.eq(self.hide_passwords), collections_groups::hide_passwords.eq(self.hide_passwords),
collections_groups::manage.eq(self.manage),
)) ))
.execute(conn) .execute(conn)
.map_res("Error adding group to collection") .map_res("Error adding group to collection")

Datei anzeigen

@ -522,13 +522,13 @@ impl Membership {
.await .await
.into_iter() .into_iter()
.filter_map(|c| { .filter_map(|c| {
let (read_only, hide_passwords, can_manage) = if self.has_full_access() { let (read_only, hide_passwords, manage) = if self.has_full_access() {
(false, false, self.atype >= MembershipType::Manager) (false, false, self.atype >= MembershipType::Manager)
} else if let Some(cu) = cu.get(&c.uuid) { } else if let Some(cu) = cu.get(&c.uuid) {
( (
cu.read_only, cu.read_only,
cu.hide_passwords, cu.hide_passwords,
self.atype == MembershipType::Manager && !cu.read_only && !cu.hide_passwords, cu.manage || (self.atype == MembershipType::Manager && !cu.read_only && !cu.hide_passwords),
) )
// If previous checks failed it might be that this user has access via a group, but we should not return those elements here // If previous checks failed it might be that this user has access via a group, but we should not return those elements here
// Those are returned via a special group endpoint // Those are returned via a special group endpoint
@ -542,7 +542,7 @@ impl Membership {
"id": c.uuid, "id": c.uuid,
"readOnly": read_only, "readOnly": read_only,
"hidePasswords": hide_passwords, "hidePasswords": hide_passwords,
"manage": can_manage, "manage": manage,
})) }))
}) })
.collect() .collect()
@ -611,6 +611,7 @@ impl Membership {
"id": self.uuid, "id": self.uuid,
"readOnly": col_user.read_only, "readOnly": col_user.read_only,
"hidePasswords": col_user.hide_passwords, "hidePasswords": col_user.hide_passwords,
"manage": col_user.manage,
}) })
} }
@ -622,11 +623,12 @@ impl Membership {
CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn).await; CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn).await;
collections collections
.iter() .iter()
.map(|c| { .map(|cu| {
json!({ json!({
"id": c.collection_uuid, "id": cu.collection_uuid,
"readOnly": c.read_only, "readOnly": cu.read_only,
"hidePasswords": c.hide_passwords, "hidePasswords": cu.hide_passwords,
"manage": cu.manage,
}) })
}) })
.collect() .collect()

Datei anzeigen

@ -226,6 +226,7 @@ table! {
collection_uuid -> Text, collection_uuid -> Text,
read_only -> Bool, read_only -> Bool,
hide_passwords -> Bool, hide_passwords -> Bool,
manage -> Bool,
} }
} }
@ -295,6 +296,7 @@ table! {
groups_uuid -> Text, groups_uuid -> Text,
read_only -> Bool, read_only -> Bool,
hide_passwords -> Bool, hide_passwords -> Bool,
manage -> Bool,
} }
} }

Datei anzeigen

@ -226,6 +226,7 @@ table! {
collection_uuid -> Text, collection_uuid -> Text,
read_only -> Bool, read_only -> Bool,
hide_passwords -> Bool, hide_passwords -> Bool,
manage -> Bool,
} }
} }
@ -295,6 +296,7 @@ table! {
groups_uuid -> Text, groups_uuid -> Text,
read_only -> Bool, read_only -> Bool,
hide_passwords -> Bool, hide_passwords -> Bool,
manage -> Bool,
} }
} }

Datei anzeigen

@ -226,6 +226,7 @@ table! {
collection_uuid -> Text, collection_uuid -> Text,
read_only -> Bool, read_only -> Bool,
hide_passwords -> Bool, hide_passwords -> Bool,
manage -> Bool,
} }
} }
@ -295,6 +296,7 @@ table! {
groups_uuid -> Text, groups_uuid -> Text,
read_only -> Bool, read_only -> Bool,
hide_passwords -> Bool, hide_passwords -> Bool,
manage -> Bool,
} }
} }