Spiegel von
https://github.com/dani-garcia/vaultwarden.git
synchronisiert 2025-01-07 11:45:40 +01:00
introduce collection_id newtype
Dieser Commit ist enthalten in:
Ursprung
16afe10898
Commit
7f2db91f23
9 geänderte Dateien mit 201 neuen und 117 gelöschten Zeilen
|
@ -368,7 +368,7 @@ pub async fn update_cipher_from_data(
|
|||
cipher: &mut Cipher,
|
||||
data: CipherData,
|
||||
headers: &Headers,
|
||||
shared_to_collections: Option<Vec<String>>,
|
||||
shared_to_collections: Option<Vec<CollectionId>>,
|
||||
conn: &mut DbConn,
|
||||
nt: &Notify<'_>,
|
||||
ut: UpdateType,
|
||||
|
@ -710,7 +710,7 @@ async fn put_cipher_partial(
|
|||
#[serde(rename_all = "camelCase")]
|
||||
struct CollectionsAdminData {
|
||||
#[serde(alias = "CollectionIds")]
|
||||
collection_ids: Vec<String>,
|
||||
collection_ids: Vec<CollectionId>,
|
||||
}
|
||||
|
||||
#[put("/ciphers/<uuid>/collections_v2", data = "<data>")]
|
||||
|
@ -769,9 +769,9 @@ async fn post_collections_update(
|
|||
err!("Cipher is not write accessible")
|
||||
}
|
||||
|
||||
let posted_collections = HashSet::<String>::from_iter(data.collection_ids);
|
||||
let posted_collections = HashSet::<CollectionId>::from_iter(data.collection_ids);
|
||||
let current_collections =
|
||||
HashSet::<String>::from_iter(cipher.get_collections(headers.user.uuid.to_string(), &mut conn).await);
|
||||
HashSet::<CollectionId>::from_iter(cipher.get_collections(headers.user.uuid.to_string(), &mut conn).await);
|
||||
|
||||
for collection in posted_collections.symmetric_difference(¤t_collections) {
|
||||
match Collection::find_by_uuid_and_org(collection, cipher.organization_uuid.as_ref().unwrap(), &mut conn).await
|
||||
|
@ -846,9 +846,10 @@ async fn post_collections_admin(
|
|||
err!("Cipher is not write accessible")
|
||||
}
|
||||
|
||||
let posted_collections = HashSet::<String>::from_iter(data.collection_ids);
|
||||
let current_collections =
|
||||
HashSet::<String>::from_iter(cipher.get_admin_collections(headers.user.uuid.to_string(), &mut conn).await);
|
||||
let posted_collections = HashSet::<CollectionId>::from_iter(data.collection_ids);
|
||||
let current_collections = HashSet::<CollectionId>::from_iter(
|
||||
cipher.get_admin_collections(headers.user.uuid.to_string(), &mut conn).await,
|
||||
);
|
||||
|
||||
for collection in posted_collections.symmetric_difference(¤t_collections) {
|
||||
match Collection::find_by_uuid_and_org(collection, cipher.organization_uuid.as_ref().unwrap(), &mut conn).await
|
||||
|
@ -900,7 +901,7 @@ struct ShareCipherData {
|
|||
#[serde(alias = "Cipher")]
|
||||
cipher: CipherData,
|
||||
#[serde(alias = "CollectionIds")]
|
||||
collection_ids: Vec<String>,
|
||||
collection_ids: Vec<CollectionId>,
|
||||
}
|
||||
|
||||
#[post("/ciphers/<uuid>/share", data = "<data>")]
|
||||
|
@ -933,7 +934,7 @@ async fn put_cipher_share(
|
|||
#[serde(rename_all = "camelCase")]
|
||||
struct ShareSelectedCipherData {
|
||||
ciphers: Vec<CipherData>,
|
||||
collection_ids: Vec<String>,
|
||||
collection_ids: Vec<CollectionId>,
|
||||
}
|
||||
|
||||
#[put("/ciphers/share", data = "<data>")]
|
||||
|
@ -1834,10 +1835,10 @@ pub struct CipherSyncData {
|
|||
pub cipher_attachments: HashMap<String, Vec<Attachment>>,
|
||||
pub cipher_folders: HashMap<String, String>,
|
||||
pub cipher_favorites: HashSet<String>,
|
||||
pub cipher_collections: HashMap<String, Vec<String>>,
|
||||
pub cipher_collections: HashMap<String, Vec<CollectionId>>,
|
||||
pub members: HashMap<OrganizationId, Membership>,
|
||||
pub user_collections: HashMap<String, CollectionUser>,
|
||||
pub user_collections_groups: HashMap<String, CollectionGroup>,
|
||||
pub user_collections: HashMap<CollectionId, CollectionUser>,
|
||||
pub user_collections_groups: HashMap<CollectionId, CollectionGroup>,
|
||||
pub user_group_full_access_for_organizations: HashSet<OrganizationId>,
|
||||
}
|
||||
|
||||
|
@ -1878,7 +1879,7 @@ impl CipherSyncData {
|
|||
|
||||
// Generate a HashMap with the Cipher UUID as key and one or more Collection UUID's
|
||||
let user_cipher_collections = Cipher::get_collections_with_cipher_by_user(user_uuid.to_string(), conn).await;
|
||||
let mut cipher_collections: HashMap<String, Vec<String>> =
|
||||
let mut cipher_collections: HashMap<String, Vec<CollectionId>> =
|
||||
HashMap::with_capacity(user_cipher_collections.len());
|
||||
for (cipher, collection) in user_cipher_collections {
|
||||
cipher_collections.entry(cipher).or_default().push(collection);
|
||||
|
@ -1889,14 +1890,14 @@ impl CipherSyncData {
|
|||
Membership::find_by_user(user_uuid, conn).await.into_iter().map(|m| (m.org_uuid.clone(), m)).collect();
|
||||
|
||||
// Generate a HashMap with the User_Collections UUID as key and the CollectionUser record
|
||||
let user_collections: HashMap<String, CollectionUser> = CollectionUser::find_by_user(user_uuid, conn)
|
||||
let user_collections: HashMap<CollectionId, CollectionUser> = CollectionUser::find_by_user(user_uuid, conn)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|uc| (uc.collection_uuid.clone(), uc))
|
||||
.collect();
|
||||
|
||||
// Generate a HashMap with the collections_uuid as key and the CollectionGroup record
|
||||
let user_collections_groups: HashMap<String, CollectionGroup> = if CONFIG.org_groups_enabled() {
|
||||
let user_collections_groups: HashMap<CollectionId, CollectionGroup> = if CONFIG.org_groups_enabled() {
|
||||
CollectionGroup::find_by_user(user_uuid, conn)
|
||||
.await
|
||||
.into_iter()
|
||||
|
|
|
@ -126,7 +126,7 @@ struct NewCollectionData {
|
|||
name: String,
|
||||
groups: Vec<NewCollectionObjectData>,
|
||||
users: Vec<NewCollectionMemberData>,
|
||||
id: Option<String>,
|
||||
id: Option<CollectionId>,
|
||||
external_id: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -340,7 +340,7 @@ async fn get_org_collections_details(
|
|||
};
|
||||
|
||||
// get all collection memberships for the current organization
|
||||
let coll_users = CollectionUser::find_by_organization(&org_id, &mut conn).await;
|
||||
let col_users = CollectionUser::find_by_organization(&org_id, &mut conn).await;
|
||||
|
||||
// check if current user has full access to the organization (either directly or via any group)
|
||||
let has_full_access_to_org = member.access_all
|
||||
|
@ -355,7 +355,7 @@ async fn get_org_collections_details(
|
|||
&& GroupUser::has_access_to_collection_by_member(&col.uuid, &member.uuid, &mut conn).await);
|
||||
|
||||
// get the users assigned directly to the given collection
|
||||
let users: Vec<Value> = coll_users
|
||||
let users: Vec<Value> = col_users
|
||||
.iter()
|
||||
.filter(|collection_user| collection_user.collection_uuid == col.uuid)
|
||||
.map(|collection_user| UserSelection::to_collection_user_details_read_only(collection_user).to_json())
|
||||
|
@ -450,7 +450,7 @@ async fn post_organization_collections(
|
|||
#[put("/organizations/<org_id>/collections/<col_id>", data = "<data>")]
|
||||
async fn put_organization_collection_update(
|
||||
org_id: OrganizationId,
|
||||
col_id: &str,
|
||||
col_id: CollectionId,
|
||||
headers: ManagerHeaders,
|
||||
data: Json<NewCollectionData>,
|
||||
conn: DbConn,
|
||||
|
@ -461,7 +461,7 @@ async fn put_organization_collection_update(
|
|||
#[post("/organizations/<org_id>/collections/<col_id>", data = "<data>")]
|
||||
async fn post_organization_collection_update(
|
||||
org_id: OrganizationId,
|
||||
col_id: &str,
|
||||
col_id: CollectionId,
|
||||
headers: ManagerHeaders,
|
||||
data: Json<NewCollectionData>,
|
||||
mut conn: DbConn,
|
||||
|
@ -472,7 +472,7 @@ async fn post_organization_collection_update(
|
|||
err!("Can't find organization details")
|
||||
};
|
||||
|
||||
let Some(mut collection) = Collection::find_by_uuid_and_org(col_id, &org_id, &mut conn).await else {
|
||||
let Some(mut collection) = Collection::find_by_uuid_and_org(&col_id, &org_id, &mut conn).await else {
|
||||
err!("Collection not found")
|
||||
};
|
||||
|
||||
|
@ -495,15 +495,13 @@ async fn post_organization_collection_update(
|
|||
)
|
||||
.await;
|
||||
|
||||
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 {
|
||||
CollectionGroup::new(String::from(col_id), 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).save(&mut conn).await?;
|
||||
}
|
||||
|
||||
CollectionUser::delete_all_by_collection(col_id, &mut conn).await?;
|
||||
CollectionUser::delete_all_by_collection(&col_id, &mut conn).await?;
|
||||
|
||||
for user in data.users {
|
||||
let Some(member) = Membership::find_by_uuid_and_org(&user.id, &org_id, &mut conn).await else {
|
||||
|
@ -514,7 +512,7 @@ async fn post_organization_collection_update(
|
|||
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, &mut conn).await?;
|
||||
}
|
||||
|
||||
Ok(Json(collection.to_json_details(&headers.user.uuid, None, &mut conn).await))
|
||||
|
@ -523,12 +521,12 @@ async fn post_organization_collection_update(
|
|||
#[delete("/organizations/<org_id>/collections/<col_id>/user/<member_id>")]
|
||||
async fn delete_organization_collection_user(
|
||||
org_id: OrganizationId,
|
||||
col_id: &str,
|
||||
col_id: CollectionId,
|
||||
member_id: MembershipId,
|
||||
_headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
let Some(collection) = Collection::find_by_uuid_and_org(col_id, &org_id, &mut conn).await else {
|
||||
let Some(collection) = Collection::find_by_uuid_and_org(&col_id, &org_id, &mut conn).await else {
|
||||
err!("Collection not found", "Collection does not exist or does not belong to this organization")
|
||||
};
|
||||
|
||||
|
@ -546,7 +544,7 @@ async fn delete_organization_collection_user(
|
|||
#[post("/organizations/<org_id>/collections/<col_id>/delete-user/<member_id>")]
|
||||
async fn post_organization_collection_delete_user(
|
||||
org_id: OrganizationId,
|
||||
col_id: &str,
|
||||
col_id: CollectionId,
|
||||
member_id: MembershipId,
|
||||
headers: AdminHeaders,
|
||||
conn: DbConn,
|
||||
|
@ -556,7 +554,7 @@ async fn post_organization_collection_delete_user(
|
|||
|
||||
async fn _delete_organization_collection(
|
||||
org_id: &OrganizationId,
|
||||
col_id: &str,
|
||||
col_id: &CollectionId,
|
||||
headers: &ManagerHeaders,
|
||||
conn: &mut DbConn,
|
||||
) -> EmptyResult {
|
||||
|
@ -579,11 +577,11 @@ async fn _delete_organization_collection(
|
|||
#[delete("/organizations/<org_id>/collections/<col_id>")]
|
||||
async fn delete_organization_collection(
|
||||
org_id: OrganizationId,
|
||||
col_id: &str,
|
||||
col_id: CollectionId,
|
||||
headers: ManagerHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
_delete_organization_collection(&org_id, col_id, &headers, &mut conn).await
|
||||
_delete_organization_collection(&org_id, &col_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
@ -598,17 +596,17 @@ struct DeleteCollectionData {
|
|||
#[post("/organizations/<org_id>/collections/<col_id>/delete")]
|
||||
async fn post_organization_collection_delete(
|
||||
org_id: OrganizationId,
|
||||
col_id: &str,
|
||||
col_id: CollectionId,
|
||||
headers: ManagerHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
_delete_organization_collection(&org_id, col_id, &headers, &mut conn).await
|
||||
_delete_organization_collection(&org_id, &col_id, &headers, &mut conn).await
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct BulkCollectionIds {
|
||||
ids: Vec<String>,
|
||||
ids: Vec<CollectionId>,
|
||||
}
|
||||
|
||||
#[delete("/organizations/<org_id>/collections", data = "<data>")]
|
||||
|
@ -630,14 +628,14 @@ async fn bulk_delete_organization_collections(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[get("/organizations/<org_id>/collections/<coll_id>/details")]
|
||||
#[get("/organizations/<org_id>/collections/<col_id>/details")]
|
||||
async fn get_org_collection_detail(
|
||||
org_id: OrganizationId,
|
||||
coll_id: &str,
|
||||
col_id: CollectionId,
|
||||
headers: ManagerHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
match Collection::find_by_uuid_and_user(coll_id, headers.user.uuid.clone(), &mut conn).await {
|
||||
match Collection::find_by_uuid_and_user(&col_id, headers.user.uuid.clone(), &mut conn).await {
|
||||
None => err!("Collection not found"),
|
||||
Some(collection) => {
|
||||
if collection.org_uuid != org_id {
|
||||
|
@ -684,15 +682,15 @@ async fn get_org_collection_detail(
|
|||
}
|
||||
}
|
||||
|
||||
#[get("/organizations/<org_id>/collections/<coll_id>/users")]
|
||||
#[get("/organizations/<org_id>/collections/<col_id>/users")]
|
||||
async fn get_collection_users(
|
||||
org_id: OrganizationId,
|
||||
coll_id: &str,
|
||||
col_id: CollectionId,
|
||||
_headers: ManagerHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
// Get org and collection, check that collection is from org
|
||||
let Some(collection) = Collection::find_by_uuid_and_org(coll_id, &org_id, &mut conn).await else {
|
||||
let Some(collection) = Collection::find_by_uuid_and_org(&col_id, &org_id, &mut conn).await else {
|
||||
err!("Collection not found in Organization")
|
||||
};
|
||||
|
||||
|
@ -709,21 +707,21 @@ async fn get_collection_users(
|
|||
Ok(Json(json!(user_list)))
|
||||
}
|
||||
|
||||
#[put("/organizations/<org_id>/collections/<coll_id>/users", data = "<data>")]
|
||||
#[put("/organizations/<org_id>/collections/<col_id>/users", data = "<data>")]
|
||||
async fn put_collection_users(
|
||||
org_id: OrganizationId,
|
||||
coll_id: &str,
|
||||
data: Json<Vec<CollectionData>>,
|
||||
col_id: CollectionId,
|
||||
data: Json<Vec<MembershipData>>,
|
||||
_headers: ManagerHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
// Get org and collection, check that collection is from org
|
||||
if Collection::find_by_uuid_and_org(coll_id, &org_id, &mut conn).await.is_none() {
|
||||
if Collection::find_by_uuid_and_org(&col_id, &org_id, &mut conn).await.is_none() {
|
||||
err!("Collection not found in Organization")
|
||||
}
|
||||
|
||||
// Delete all the user-collections
|
||||
CollectionUser::delete_all_by_collection(coll_id, &mut conn).await?;
|
||||
CollectionUser::delete_all_by_collection(&col_id, &mut conn).await?;
|
||||
|
||||
// And then add all the received ones (except if the user has access_all)
|
||||
for d in data.iter() {
|
||||
|
@ -735,7 +733,7 @@ async fn put_collection_users(
|
|||
continue;
|
||||
}
|
||||
|
||||
CollectionUser::save(&user.user_uuid, coll_id, d.read_only, d.hide_passwords, &mut conn).await?;
|
||||
CollectionUser::save(&user.user_uuid, &col_id, d.read_only, d.hide_passwords, &mut conn).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -841,6 +839,14 @@ async fn post_org_keys(
|
|||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CollectionData {
|
||||
id: CollectionId,
|
||||
read_only: bool,
|
||||
hide_passwords: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct MembershipData {
|
||||
id: MembershipId,
|
||||
read_only: bool,
|
||||
hide_passwords: bool,
|
||||
|
@ -1615,14 +1621,14 @@ async fn post_org_import(
|
|||
// TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
|
||||
Cipher::validate_cipher_data(&data.ciphers)?;
|
||||
|
||||
let existing_collections: HashSet<Option<String>> =
|
||||
Collection::find_by_organization(&org_id, &mut conn).await.into_iter().map(|c| (Some(c.uuid))).collect();
|
||||
let mut collections: Vec<String> = Vec::with_capacity(data.collections.len());
|
||||
for coll in data.collections {
|
||||
let collection_uuid = if existing_collections.contains(&coll.id) {
|
||||
coll.id.unwrap()
|
||||
let existing_collections: HashSet<Option<CollectionId>> =
|
||||
Collection::find_by_organization(&org_id, &mut conn).await.into_iter().map(|c| Some(c.uuid)).collect();
|
||||
let mut collections: Vec<CollectionId> = Vec::with_capacity(data.collections.len());
|
||||
for col in data.collections {
|
||||
let collection_uuid = if existing_collections.contains(&col.id) {
|
||||
col.id.unwrap()
|
||||
} else {
|
||||
let new_collection = Collection::new(org_id.clone(), coll.name, coll.external_id);
|
||||
let new_collection = Collection::new(org_id.clone(), col.name, col.external_id);
|
||||
new_collection.save(&mut conn).await?;
|
||||
new_collection.uuid
|
||||
};
|
||||
|
@ -1649,10 +1655,10 @@ async fn post_org_import(
|
|||
}
|
||||
|
||||
// Assign the collections
|
||||
for (cipher_index, coll_index) in relations {
|
||||
for (cipher_index, col_index) in relations {
|
||||
let cipher_id = &ciphers[cipher_index];
|
||||
let coll_id = &collections[coll_index];
|
||||
CollectionCipher::save(cipher_id, coll_id, &mut conn).await?;
|
||||
let col_id = &collections[col_index];
|
||||
CollectionCipher::save(cipher_id, col_id, &mut conn).await?;
|
||||
}
|
||||
|
||||
let mut user = headers.user;
|
||||
|
@ -1665,7 +1671,7 @@ async fn post_org_import(
|
|||
struct BulkCollectionsData {
|
||||
organization_id: OrganizationId,
|
||||
cipher_ids: Vec<String>,
|
||||
collection_ids: HashSet<String>,
|
||||
collection_ids: HashSet<CollectionId>,
|
||||
remove_collections: bool,
|
||||
}
|
||||
|
||||
|
@ -1683,7 +1689,7 @@ async fn post_bulk_collections(data: Json<BulkCollectionsData>, headers: Headers
|
|||
|
||||
// Get all the collection available to the user in one query
|
||||
// Also filter based upon the provided collections
|
||||
let user_collections: HashMap<String, Collection> =
|
||||
let user_collections: HashMap<CollectionId, Collection> =
|
||||
Collection::find_by_organization_and_user_uuid(&data.organization_id, &headers.user.uuid, &mut conn)
|
||||
.await
|
||||
.into_iter()
|
||||
|
@ -2352,7 +2358,7 @@ struct GroupRequest {
|
|||
#[serde(default)]
|
||||
access_all: bool,
|
||||
external_id: Option<String>,
|
||||
collections: Vec<SelectionReadOnly>,
|
||||
collections: Vec<CollectionSelection>,
|
||||
users: Vec<MembershipId>,
|
||||
}
|
||||
|
||||
|
@ -2380,10 +2386,6 @@ struct SelectionReadOnly {
|
|||
}
|
||||
|
||||
impl SelectionReadOnly {
|
||||
pub fn to_collection_group(&self, groups_uuid: String) -> CollectionGroup {
|
||||
CollectionGroup::new(self.id.clone(), groups_uuid, self.read_only, self.hide_passwords)
|
||||
}
|
||||
|
||||
pub fn to_collection_group_details_read_only(collection_group: &CollectionGroup) -> Self {
|
||||
Self {
|
||||
id: collection_group.groups_uuid.clone(),
|
||||
|
@ -2397,6 +2399,20 @@ impl SelectionReadOnly {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CollectionSelection {
|
||||
id: CollectionId,
|
||||
read_only: bool,
|
||||
hide_passwords: bool,
|
||||
}
|
||||
|
||||
impl CollectionSelection {
|
||||
pub fn to_collection_group(&self, groups_uuid: String) -> CollectionGroup {
|
||||
CollectionGroup::new(self.id.clone(), groups_uuid, self.read_only, self.hide_passwords)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct UserSelection {
|
||||
|
@ -2496,7 +2512,7 @@ async fn put_group(
|
|||
|
||||
async fn add_update_group(
|
||||
mut group: Group,
|
||||
collections: Vec<SelectionReadOnly>,
|
||||
collections: Vec<CollectionSelection>,
|
||||
members: Vec<MembershipId>,
|
||||
org_id: OrganizationId,
|
||||
headers: &AdminHeaders,
|
||||
|
@ -2504,8 +2520,8 @@ async fn add_update_group(
|
|||
) -> JsonResult {
|
||||
group.save(conn).await?;
|
||||
|
||||
for selection_read_only_request in collections {
|
||||
let mut collection_group = selection_read_only_request.to_collection_group(group.uuid.clone());
|
||||
for col_selection in collections {
|
||||
let mut collection_group = col_selection.to_collection_group(group.uuid.clone());
|
||||
collection_group.save(conn).await?;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use rocket_ws::{Message, WebSocket};
|
|||
use crate::{
|
||||
auth::{ClientIp, WsAccessTokenHeader},
|
||||
db::{
|
||||
models::{Cipher, Folder, Send as DbSend, User, UserId},
|
||||
models::{Cipher, CollectionId, Folder, Send as DbSend, User, UserId},
|
||||
DbConn,
|
||||
},
|
||||
Error, CONFIG,
|
||||
|
@ -415,7 +415,7 @@ impl WebSocketUsers {
|
|||
cipher: &Cipher,
|
||||
user_uuids: &[UserId],
|
||||
acting_device_uuid: &String,
|
||||
collection_uuids: Option<Vec<String>>,
|
||||
collection_uuids: Option<Vec<CollectionId>>,
|
||||
conn: &mut DbConn,
|
||||
) {
|
||||
// Skip any processing if both WebSockets and Push are not active
|
||||
|
@ -428,7 +428,7 @@ impl WebSocketUsers {
|
|||
let (user_uuid, collection_uuids, revision_date) = if let Some(collection_uuids) = collection_uuids {
|
||||
(
|
||||
Value::Nil,
|
||||
Value::Array(collection_uuids.into_iter().map(|v| v.into()).collect::<Vec<Value>>()),
|
||||
Value::Array(collection_uuids.into_iter().map(|v| v.to_string().into()).collect::<Vec<Value>>()),
|
||||
serialize_date(Utc::now().naive_utc()),
|
||||
)
|
||||
} else {
|
||||
|
|
12
src/auth.rs
12
src/auth.rs
|
@ -14,7 +14,7 @@ use std::{
|
|||
net::IpAddr,
|
||||
};
|
||||
|
||||
use crate::db::models::{MembershipId, OrganizationId, UserId};
|
||||
use crate::db::models::{CollectionId, MembershipId, OrganizationId, UserId};
|
||||
use crate::{error::Error, CONFIG};
|
||||
|
||||
const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
|
||||
|
@ -649,16 +649,16 @@ impl From<AdminHeaders> for Headers {
|
|||
// col_id is usually the fourth path param ("/organizations/<org_id>/collections/<col_id>"),
|
||||
// but there could be cases where it is a query value.
|
||||
// First check the path, if this is not a valid uuid, try the query values.
|
||||
fn get_col_id(request: &Request<'_>) -> Option<String> {
|
||||
fn get_col_id(request: &Request<'_>) -> Option<CollectionId> {
|
||||
if let Some(Ok(col_id)) = request.param::<String>(3) {
|
||||
if uuid::Uuid::parse_str(&col_id).is_ok() {
|
||||
return Some(col_id);
|
||||
return Some(col_id.into());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Ok(col_id)) = request.query_value::<String>("collectionId") {
|
||||
if uuid::Uuid::parse_str(&col_id).is_ok() {
|
||||
return Some(col_id);
|
||||
return Some(col_id.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -763,11 +763,11 @@ impl From<ManagerHeadersLoose> for Headers {
|
|||
impl ManagerHeaders {
|
||||
pub async fn from_loose(
|
||||
h: ManagerHeadersLoose,
|
||||
collections: &Vec<String>,
|
||||
collections: &Vec<CollectionId>,
|
||||
conn: &mut DbConn,
|
||||
) -> Result<ManagerHeaders, Error> {
|
||||
for col_id in collections {
|
||||
if uuid::Uuid::parse_str(col_id).is_err() {
|
||||
if uuid::Uuid::parse_str(col_id.as_ref()).is_err() {
|
||||
err!("Collection Id is malformed!");
|
||||
}
|
||||
if !Collection::can_access_collection(&h.membership, col_id, conn).await {
|
||||
|
|
|
@ -4,8 +4,8 @@ use chrono::{NaiveDateTime, TimeDelta, Utc};
|
|||
use serde_json::Value;
|
||||
|
||||
use super::{
|
||||
Attachment, CollectionCipher, Favorite, FolderCipher, Group, Membership, MembershipStatus, MembershipType,
|
||||
OrganizationId, User, UserId,
|
||||
Attachment, CollectionCipher, CollectionId, Favorite, FolderCipher, Group, Membership, MembershipStatus,
|
||||
MembershipType, OrganizationId, User, UserId,
|
||||
};
|
||||
|
||||
use crate::api::core::{CipherData, CipherSyncData, CipherSyncType};
|
||||
|
@ -862,7 +862,7 @@ impl Cipher {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn get_collections(&self, user_id: String, conn: &mut DbConn) -> Vec<String> {
|
||||
pub async fn get_collections(&self, user_id: String, conn: &mut DbConn) -> Vec<CollectionId> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! {conn: {
|
||||
ciphers_collections::table
|
||||
|
@ -894,7 +894,7 @@ impl Cipher {
|
|||
.and(collections_groups::read_only.eq(false)))
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<String>(conn).unwrap_or_default()
|
||||
.load::<CollectionId>(conn).unwrap_or_default()
|
||||
}}
|
||||
} else {
|
||||
db_run! {conn: {
|
||||
|
@ -916,12 +916,12 @@ impl Cipher {
|
|||
.and(users_collections::read_only.eq(false)))
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<String>(conn).unwrap_or_default()
|
||||
.load::<CollectionId>(conn).unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_admin_collections(&self, user_id: String, conn: &mut DbConn) -> Vec<String> {
|
||||
pub async fn get_admin_collections(&self, user_id: String, conn: &mut DbConn) -> Vec<CollectionId> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! {conn: {
|
||||
ciphers_collections::table
|
||||
|
@ -954,7 +954,7 @@ impl Cipher {
|
|||
.or(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<String>(conn).unwrap_or_default()
|
||||
.load::<CollectionId>(conn).unwrap_or_default()
|
||||
}}
|
||||
} else {
|
||||
db_run! {conn: {
|
||||
|
@ -977,14 +977,17 @@ impl Cipher {
|
|||
.or(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<String>(conn).unwrap_or_default()
|
||||
.load::<CollectionId>(conn).unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a Vec with (cipher_uuid, collection_uuid)
|
||||
/// This is used during a full sync so we only need one query for all collections accessible.
|
||||
pub async fn get_collections_with_cipher_by_user(user_id: String, conn: &mut DbConn) -> Vec<(String, String)> {
|
||||
pub async fn get_collections_with_cipher_by_user(
|
||||
user_id: String,
|
||||
conn: &mut DbConn,
|
||||
) -> Vec<(String, CollectionId)> {
|
||||
db_run! {conn: {
|
||||
ciphers_collections::table
|
||||
.inner_join(collections::table.on(
|
||||
|
@ -1018,7 +1021,7 @@ impl Cipher {
|
|||
.or_filter(collections_groups::collections_uuid.is_not_null()) //Access via group
|
||||
.select(ciphers_collections::all_columns)
|
||||
.distinct()
|
||||
.load::<(String, String)>(conn).unwrap_or_default()
|
||||
.load::<(String, CollectionId)>(conn).unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
use rocket::request::FromParam;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
fmt::{Display, Formatter},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use super::{CollectionGroup, GroupUser, Membership, MembershipStatus, MembershipType, OrganizationId, User, UserId};
|
||||
use crate::CONFIG;
|
||||
|
@ -8,7 +14,7 @@ db_object! {
|
|||
#[diesel(table_name = collections)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Collection {
|
||||
pub uuid: String,
|
||||
pub uuid: CollectionId,
|
||||
pub org_uuid: OrganizationId,
|
||||
pub name: String,
|
||||
pub external_id: Option<String>,
|
||||
|
@ -19,7 +25,7 @@ db_object! {
|
|||
#[diesel(primary_key(user_uuid, collection_uuid))]
|
||||
pub struct CollectionUser {
|
||||
pub user_uuid: UserId,
|
||||
pub collection_uuid: String,
|
||||
pub collection_uuid: CollectionId,
|
||||
pub read_only: bool,
|
||||
pub hide_passwords: bool,
|
||||
}
|
||||
|
@ -29,7 +35,7 @@ db_object! {
|
|||
#[diesel(primary_key(cipher_uuid, collection_uuid))]
|
||||
pub struct CollectionCipher {
|
||||
pub cipher_uuid: String,
|
||||
pub collection_uuid: String,
|
||||
pub collection_uuid: CollectionId,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +43,7 @@ db_object! {
|
|||
impl Collection {
|
||||
pub fn new(org_uuid: OrganizationId, name: String, external_id: Option<String>) -> Self {
|
||||
let mut new_model = Self {
|
||||
uuid: crate::util::get_uuid(),
|
||||
uuid: CollectionId(crate::util::get_uuid()),
|
||||
org_uuid,
|
||||
name,
|
||||
external_id: None,
|
||||
|
@ -121,7 +127,7 @@ impl Collection {
|
|||
json_object
|
||||
}
|
||||
|
||||
pub async fn can_access_collection(member: &Membership, col_id: &str, conn: &mut DbConn) -> bool {
|
||||
pub async fn can_access_collection(member: &Membership, col_id: &CollectionId, conn: &mut DbConn) -> bool {
|
||||
member.has_status(MembershipStatus::Confirmed)
|
||||
&& (member.has_full_access()
|
||||
|| CollectionUser::has_access_to_collection_by_user(col_id, &member.user_uuid, conn).await
|
||||
|
@ -198,7 +204,7 @@ impl Collection {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_uuid(uuid: &CollectionId, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
.filter(collections::uuid.eq(uuid))
|
||||
|
@ -312,7 +318,11 @@ impl Collection {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_uuid_and_org(
|
||||
uuid: &CollectionId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
.filter(collections::uuid.eq(uuid))
|
||||
|
@ -324,7 +334,7 @@ impl Collection {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: UserId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_uuid_and_user(uuid: &CollectionId, user_uuid: UserId, conn: &mut DbConn) -> Option<Self> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
|
@ -534,7 +544,7 @@ impl CollectionUser {
|
|||
|
||||
pub async fn save(
|
||||
user_uuid: &UserId,
|
||||
collection_uuid: &str,
|
||||
collection_uuid: &CollectionId,
|
||||
read_only: bool,
|
||||
hide_passwords: bool,
|
||||
conn: &mut DbConn,
|
||||
|
@ -604,7 +614,7 @@ impl CollectionUser {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_collection(collection_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_collections::table
|
||||
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
||||
|
@ -616,7 +626,7 @@ impl CollectionUser {
|
|||
}
|
||||
|
||||
pub async fn find_by_collection_swap_user_uuid_with_member_uuid(
|
||||
collection_uuid: &str,
|
||||
collection_uuid: &CollectionId,
|
||||
conn: &mut DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
|
@ -631,7 +641,7 @@ impl CollectionUser {
|
|||
}
|
||||
|
||||
pub async fn find_by_collection_and_user(
|
||||
collection_uuid: &str,
|
||||
collection_uuid: &CollectionId,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
) -> Option<Self> {
|
||||
|
@ -657,7 +667,7 @@ impl CollectionUser {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
for collection in CollectionUser::find_by_collection(collection_uuid, conn).await.iter() {
|
||||
User::update_uuid_revision(&collection.user_uuid, conn).await;
|
||||
}
|
||||
|
@ -689,14 +699,18 @@ impl CollectionUser {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn has_access_to_collection_by_user(col_id: &str, user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
pub async fn has_access_to_collection_by_user(
|
||||
col_id: &CollectionId,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
) -> bool {
|
||||
Self::find_by_collection_and_user(col_id, user_uuid, conn).await.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Database methods
|
||||
impl CollectionCipher {
|
||||
pub async fn save(cipher_uuid: &str, collection_uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(cipher_uuid: &str, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
Self::update_users_revision(collection_uuid, conn).await;
|
||||
|
||||
db_run! { conn:
|
||||
|
@ -726,7 +740,7 @@ impl CollectionCipher {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(cipher_uuid: &str, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
Self::update_users_revision(collection_uuid, conn).await;
|
||||
|
||||
db_run! { conn: {
|
||||
|
@ -748,7 +762,7 @@ impl CollectionCipher {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid)))
|
||||
.execute(conn)
|
||||
|
@ -756,9 +770,57 @@ impl CollectionCipher {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn update_users_revision(collection_uuid: &str, conn: &mut DbConn) {
|
||||
pub async fn update_users_revision(collection_uuid: &CollectionId, conn: &mut DbConn) {
|
||||
if let Some(collection) = Collection::find_by_uuid(collection_uuid, conn).await {
|
||||
collection.update_users_revision(conn).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DieselNewType, FromForm, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CollectionId(String);
|
||||
|
||||
impl AsRef<str> for CollectionId {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CollectionId {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for CollectionId {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CollectionId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for CollectionId {
|
||||
fn from(raw: String) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> FromParam<'r> for CollectionId {
|
||||
type Error = ();
|
||||
|
||||
#[inline(always)]
|
||||
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
||||
if param.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-')) {
|
||||
Ok(Self(param.to_string()))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{Membership, MembershipId, OrganizationId, User, UserId};
|
||||
use super::{CollectionId, Membership, MembershipId, OrganizationId, User, UserId};
|
||||
use crate::api::EmptyResult;
|
||||
use crate::db::DbConn;
|
||||
use crate::error::MapResult;
|
||||
|
@ -23,7 +23,7 @@ db_object! {
|
|||
#[diesel(table_name = collections_groups)]
|
||||
#[diesel(primary_key(collections_uuid, groups_uuid))]
|
||||
pub struct CollectionGroup {
|
||||
pub collections_uuid: String,
|
||||
pub collections_uuid: CollectionId,
|
||||
pub groups_uuid: String,
|
||||
pub read_only: bool,
|
||||
pub hide_passwords: bool,
|
||||
|
@ -113,7 +113,7 @@ impl Group {
|
|||
}
|
||||
|
||||
impl CollectionGroup {
|
||||
pub fn new(collections_uuid: String, groups_uuid: String, read_only: bool, hide_passwords: bool) -> Self {
|
||||
pub fn new(collections_uuid: CollectionId, groups_uuid: String, read_only: bool, hide_passwords: bool) -> Self {
|
||||
Self {
|
||||
collections_uuid,
|
||||
groups_uuid,
|
||||
|
@ -370,7 +370,7 @@ impl CollectionGroup {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_collection(collection_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
collections_groups::table
|
||||
.filter(collections_groups::collections_uuid.eq(collection_uuid))
|
||||
|
@ -410,7 +410,7 @@ impl CollectionGroup {
|
|||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
let collection_assigned_to_groups = CollectionGroup::find_by_collection(collection_uuid, conn).await;
|
||||
for collection_assigned_to_group in collection_assigned_to_groups {
|
||||
let group_users = GroupUser::find_by_group(&collection_assigned_to_group.groups_uuid, conn).await;
|
||||
|
@ -496,7 +496,7 @@ impl GroupUser {
|
|||
}
|
||||
|
||||
pub async fn has_access_to_collection_by_member(
|
||||
collection_uuid: &str,
|
||||
collection_uuid: &CollectionId,
|
||||
member_uuid: &MembershipId,
|
||||
conn: &mut DbConn,
|
||||
) -> bool {
|
||||
|
|
|
@ -19,7 +19,7 @@ mod user;
|
|||
pub use self::attachment::Attachment;
|
||||
pub use self::auth_request::AuthRequest;
|
||||
pub use self::cipher::{Cipher, RepromptType};
|
||||
pub use self::collection::{Collection, CollectionCipher, CollectionUser};
|
||||
pub use self::collection::{Collection, CollectionCipher, CollectionId, CollectionUser};
|
||||
pub use self::device::{Device, DeviceType};
|
||||
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType};
|
||||
pub use self::event::{Event, EventType};
|
||||
|
|
|
@ -10,8 +10,10 @@ use std::{
|
|||
ops::Deref,
|
||||
};
|
||||
|
||||
use super::{CollectionUser, Group, GroupUser, OrgPolicy, OrgPolicyType, TwoFactor, User, UserId};
|
||||
use crate::db::models::{Collection, CollectionGroup};
|
||||
use super::{
|
||||
Collection, CollectionGroup, CollectionId, CollectionUser, Group, GroupUser, OrgPolicy, OrgPolicyType, TwoFactor,
|
||||
User, UserId,
|
||||
};
|
||||
use crate::CONFIG;
|
||||
|
||||
db_object! {
|
||||
|
@ -474,7 +476,7 @@ impl Membership {
|
|||
// If collections are to be included, only include them if the user does not have full access via a group or defined to the user it self
|
||||
let collections: Vec<Value> = if include_collections && !(full_access_group || self.has_full_access()) {
|
||||
// Get all collections for the user here already to prevent more queries
|
||||
let cu: HashMap<String, CollectionUser> =
|
||||
let cu: HashMap<CollectionId, CollectionUser> =
|
||||
CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn)
|
||||
.await
|
||||
.into_iter()
|
||||
|
@ -482,7 +484,7 @@ impl Membership {
|
|||
.collect();
|
||||
|
||||
// Get all collection groups for this user to prevent there inclusion
|
||||
let cg: HashSet<String> = CollectionGroup::find_by_user(&self.user_uuid, conn)
|
||||
let cg: HashSet<CollectionId> = CollectionGroup::find_by_user(&self.user_uuid, conn)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|cg| cg.collections_uuid)
|
||||
|
@ -961,7 +963,7 @@ impl Membership {
|
|||
}
|
||||
|
||||
pub async fn find_by_collection_and_org(
|
||||
collection_uuid: &str,
|
||||
collection_uuid: &CollectionId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
) -> Vec<Self> {
|
||||
|
|
Laden …
In neuem Issue referenzieren