mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-26 07:50:02 +02:00 
			
		
		
		
	Implement Collection-Cipher mapping
This commit is contained in:
		| @@ -0,0 +1 @@ | ||||
| DROP TABLE ciphers_collections; | ||||
| @@ -0,0 +1,5 @@ | ||||
| CREATE TABLE ciphers_collections ( | ||||
|   cipher_uuid       TEXT NOT NULL REFERENCES ciphers (uuid), | ||||
|   collection_uuid TEXT NOT NULL REFERENCES collections (uuid), | ||||
|   PRIMARY KEY (cipher_uuid, collection_uuid) | ||||
| ); | ||||
| @@ -1,4 +1,5 @@ | ||||
| use std::path::Path; | ||||
| use std::collections::HashSet; | ||||
|  | ||||
| use rocket::Data; | ||||
| use rocket::http::ContentType; | ||||
| @@ -297,6 +298,46 @@ fn put_cipher(uuid: String, data: Json<CipherData>, headers: Headers, conn: DbCo | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| #[allow(non_snake_case)] | ||||
| struct CollectionsAdminData { | ||||
|     collectionIds: Vec<String>, | ||||
| } | ||||
|  | ||||
| #[post("/ciphers/<uuid>/collections-admin", data = "<data>")] | ||||
| fn post_collections_admin(uuid: String, data: Json<CollectionsAdminData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: CollectionsAdminData = data.into_inner(); | ||||
|  | ||||
|     let cipher = match Cipher::find_by_uuid(&uuid, &conn) { | ||||
|         Some(cipher) => cipher, | ||||
|         None => err!("Cipher doesn't exist") | ||||
|     }; | ||||
|  | ||||
|     if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) { | ||||
|         err!("Cipher is not write accessible") | ||||
|     } | ||||
|  | ||||
|     let posted_collections: HashSet<String> = data.collectionIds.iter().cloned().collect(); | ||||
|     let current_collections: HashSet<String> = cipher.get_collections(&conn).iter().cloned().collect(); | ||||
|  | ||||
|     //TODO: update cipher collection mapping | ||||
|     for collection in posted_collections.symmetric_difference(¤t_collections) { | ||||
|         match Collection::find_by_uuid(&collection, &conn) { | ||||
|             None => (), // Does not exist, what now? | ||||
|             Some(collection) => { | ||||
|                 if collection.is_writable_by_user(&headers.user.uuid, &conn) { | ||||
|                     if posted_collections.contains(&collection.uuid) { // Add to collection | ||||
|                         CollectionCipher::save(&cipher.uuid, &collection.uuid, &conn); | ||||
|                     } else { // Remove from collection | ||||
|                         CollectionCipher::delete(&cipher.uuid, &collection.uuid, &conn); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")] | ||||
| fn post_attachment(uuid: String, data: Data, content_type: &ContentType, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|   | ||||
| @@ -67,6 +67,7 @@ pub fn routes() -> Vec<Route> { | ||||
|         post_organization, | ||||
|         post_organization_collections, | ||||
|         post_organization_collection_update, | ||||
|         post_collections_admin, | ||||
|         get_org_details, | ||||
|         get_org_users, | ||||
|         send_invite, | ||||
|   | ||||
| @@ -98,7 +98,7 @@ impl Cipher { | ||||
|             "OrganizationId": self.organization_uuid, | ||||
|             "Attachments": attachments_json, | ||||
|             "OrganizationUseTotp": false, | ||||
|             "CollectionIds": [], | ||||
|             "CollectionIds": self.get_collections(&conn), | ||||
|  | ||||
|             "Name": self.name, | ||||
|             "Notes": self.notes, | ||||
| @@ -241,4 +241,11 @@ impl Cipher { | ||||
|             .select(ciphers::all_columns) | ||||
|             .load::<Self>(&**conn).expect("Error loading ciphers") | ||||
|     } | ||||
|  | ||||
|     pub fn get_collections(&self, conn: &DbConn) -> Vec<String> { | ||||
|         ciphers_collections::table | ||||
|         .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) | ||||
|         .select(ciphers_collections::collection_uuid) | ||||
|         .load::<String>(&**conn).unwrap_or(vec![]) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ use serde_json::Value as JsonValue; | ||||
|  | ||||
| use uuid::Uuid; | ||||
|  | ||||
| use super::Organization; | ||||
| use super::{Organization, UserOrganization}; | ||||
|  | ||||
| #[derive(Debug, Identifiable, Queryable, Insertable, Associations)] | ||||
| #[table_name = "collections"] | ||||
| @@ -100,6 +100,27 @@ impl Collection { | ||||
|             .select(collections::all_columns) | ||||
|             .first::<Self>(&**conn).ok() | ||||
|     } | ||||
|  | ||||
|     pub fn is_writable_by_user(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         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 { | ||||
|                    match 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::<Self>(&**conn).ok() { | ||||
|                        None => false, // Read only or no access to collection | ||||
|                        Some(_) => true, | ||||
|                    } | ||||
|                } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| use super::User;  | ||||
| @@ -147,4 +168,40 @@ impl CollectionUsers { | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| use super::Cipher;  | ||||
|  | ||||
| #[derive(Debug, Identifiable, Queryable, Insertable, Associations)] | ||||
| #[table_name = "ciphers_collections"] | ||||
| #[belongs_to(Cipher, foreign_key = "cipher_uuid")] | ||||
| #[belongs_to(Collection, foreign_key = "collection_uuid")] | ||||
| #[primary_key(cipher_uuid, collection_uuid)] | ||||
| pub struct CollectionCipher { | ||||
|     pub cipher_uuid: String, | ||||
|     pub collection_uuid: String, | ||||
| } | ||||
|  | ||||
| /// Database methods | ||||
| impl CollectionCipher { | ||||
|     pub fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> bool { | ||||
|         match diesel::replace_into(ciphers_collections::table) | ||||
|             .values(( | ||||
|                 ciphers_collections::cipher_uuid.eq(cipher_uuid), | ||||
|                 ciphers_collections::collection_uuid.eq(collection_uuid), | ||||
|             )).execute(&**conn) { | ||||
|             Ok(1) => true, // One row inserted | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> bool { | ||||
|         match diesel::delete(ciphers_collections::table | ||||
|             .filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)) | ||||
|             .filter(ciphers_collections::collection_uuid.eq(collection_uuid))) | ||||
|             .execute(&**conn) { | ||||
|             Ok(1) => true, // One row deleted | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -14,4 +14,4 @@ pub use self::folder::{Folder, FolderCipher}; | ||||
| pub use self::user::User; | ||||
| pub use self::organization::Organization; | ||||
| pub use self::organization::{UserOrganization, UserOrgStatus, UserOrgType}; | ||||
| pub use self::collection::{Collection, CollectionUsers}; | ||||
| pub use self::collection::{Collection, CollectionUsers, CollectionCipher}; | ||||
|   | ||||
| @@ -101,6 +101,13 @@ table! { | ||||
|     } | ||||
| } | ||||
|  | ||||
| table! { | ||||
|     ciphers_collections (cipher_uuid, collection_uuid) { | ||||
|         cipher_uuid -> Text, | ||||
|         collection_uuid -> Text, | ||||
|     } | ||||
| } | ||||
|  | ||||
| table! { | ||||
|     users_organizations (uuid) { | ||||
|         uuid -> Text, | ||||
| @@ -124,6 +131,8 @@ joinable!(folders_ciphers -> ciphers (cipher_uuid)); | ||||
| joinable!(folders_ciphers -> folders (folder_uuid)); | ||||
| joinable!(users_collections -> collections (collection_uuid)); | ||||
| joinable!(users_collections -> users (user_uuid)); | ||||
| joinable!(ciphers_collections -> collections (collection_uuid)); | ||||
| joinable!(ciphers_collections -> ciphers (cipher_uuid)); | ||||
| joinable!(users_organizations -> organizations (org_uuid)); | ||||
| joinable!(users_organizations -> users (user_uuid)); | ||||
|  | ||||
| @@ -137,5 +146,6 @@ allow_tables_to_appear_in_same_query!( | ||||
|     organizations, | ||||
|     users, | ||||
|     users_collections, | ||||
|     ciphers_collections, | ||||
|     users_organizations, | ||||
| ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user