mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-09-12 19:45:58 +03:00
group support
This commit is contained in:
@@ -2,7 +2,9 @@ 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, Group, User, UserOrgStatus, UserOrgType, UserOrganization,
|
||||
};
|
||||
|
||||
use crate::api::core::CipherSyncData;
|
||||
|
||||
@@ -337,7 +339,7 @@ impl Cipher {
|
||||
}
|
||||
|
||||
/// Returns whether this cipher is owned by an org in which the user has full access.
|
||||
pub async fn is_in_full_access_org(
|
||||
async fn is_in_full_access_org(
|
||||
&self,
|
||||
user_uuid: &str,
|
||||
cipher_sync_data: Option<&CipherSyncData>,
|
||||
@@ -355,6 +357,23 @@ impl Cipher {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns whether this cipher is owned by an group in which the user has full access.
|
||||
async fn is_in_full_access_group(
|
||||
&self,
|
||||
user_uuid: &str,
|
||||
cipher_sync_data: Option<&CipherSyncData>,
|
||||
conn: &DbConn,
|
||||
) -> bool {
|
||||
if let Some(ref org_uuid) = self.organization_uuid {
|
||||
if let Some(cipher_sync_data) = cipher_sync_data {
|
||||
return cipher_sync_data.user_group_full_access_for_organizations.get(org_uuid).is_some();
|
||||
} else {
|
||||
return Group::is_in_full_access_group(user_uuid, org_uuid, conn).await;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// not in any collection the user has access to. Otherwise, the user has
|
||||
@@ -369,7 +388,10 @@ 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
|
||||
|| self.is_in_full_access_group(user_uuid, cipher_sync_data, conn).await
|
||||
{
|
||||
return Some((false, false));
|
||||
}
|
||||
|
||||
@@ -377,14 +399,22 @@ 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(cg) = cipher_sync_data.user_collections_groups.get(collection) {
|
||||
rows.push((cg.read_only, cg.hide_passwords));
|
||||
}
|
||||
}
|
||||
}
|
||||
rows
|
||||
} else {
|
||||
self.get_collections_access_flags(user_uuid, conn).await
|
||||
let mut access_flags = self.get_user_collections_access_flags(user_uuid, conn).await;
|
||||
access_flags.append(&mut self.get_group_collections_access_flags(user_uuid, conn).await);
|
||||
access_flags
|
||||
};
|
||||
|
||||
if rows.is_empty() {
|
||||
@@ -411,7 +441,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.
|
||||
@@ -424,7 +454,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(collections_groups::table.on(
|
||||
collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid)
|
||||
))
|
||||
.inner_join(groups_users::table.on(
|
||||
groups_users::groups_uuid.eq(collections_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((collections_groups::read_only, collections_groups::hide_passwords))
|
||||
.load::<(bool, bool)>(conn)
|
||||
.expect("Error getting group access restrictions")
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -477,10 +530,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
|
||||
@@ -502,9 +555,22 @@ 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(collections_groups::table.on(
|
||||
collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid).and(
|
||||
collections_groups::groups_uuid.eq(groups::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(collections_groups::collections_uuid.is_not_null()) // Access via groups
|
||||
.into_boxed();
|
||||
|
||||
if !visible_only {
|
||||
@@ -630,11 +696,22 @@ 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(collections_groups::table.on(
|
||||
collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid).and(
|
||||
collections_groups::groups_uuid.eq(groups::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(collections_groups::collections_uuid.is_not_null()) //Access via group
|
||||
.select(ciphers_collections::all_columns)
|
||||
.load::<(String, String)>(conn).unwrap_or_default()
|
||||
}}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{User, UserOrgStatus, UserOrgType, UserOrganization};
|
||||
use super::{CollectionGroup, User, UserOrgStatus, UserOrgType, UserOrganization};
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
@@ -127,6 +127,7 @@ impl Collection {
|
||||
self.update_users_revision(conn).await;
|
||||
CollectionCipher::delete_all_by_collection(&self.uuid, conn).await?;
|
||||
CollectionUser::delete_all_by_collection(&self.uuid, conn).await?;
|
||||
CollectionGroup::delete_all_by_collection(&self.uuid, conn).await?;
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(collections::table.filter(collections::uuid.eq(self.uuid)))
|
||||
@@ -171,14 +172,33 @@ impl Collection {
|
||||
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(
|
||||
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
||||
)
|
||||
.filter(
|
||||
users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection
|
||||
users_organizations::access_all.eq(true) // access_all in Organization
|
||||
).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()
|
||||
)
|
||||
)
|
||||
).select(collections::all_columns)
|
||||
)
|
||||
.select(collections::all_columns)
|
||||
.distinct()
|
||||
.load::<CollectionDb>(conn).expect("Error loading collections").from_db()
|
||||
}}
|
||||
}
|
||||
|
501
src/db/models/group.rs
Normal file
501
src/db/models/group.rs
Normal file
@@ -0,0 +1,501 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use serde_json::Value;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[table_name = "groups"]
|
||||
#[primary_key(uuid)]
|
||||
pub struct Group {
|
||||
pub uuid: String,
|
||||
pub organizations_uuid: String,
|
||||
pub name: String,
|
||||
pub access_all: bool,
|
||||
external_id: Option<String>,
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub revision_date: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[table_name = "collections_groups"]
|
||||
#[primary_key(collections_uuid, groups_uuid)]
|
||||
pub struct CollectionGroup {
|
||||
pub collections_uuid: String,
|
||||
pub groups_uuid: String,
|
||||
pub read_only: bool,
|
||||
pub hide_passwords: bool,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[table_name = "groups_users"]
|
||||
#[primary_key(groups_uuid, users_organizations_uuid)]
|
||||
pub struct GroupUser {
|
||||
pub groups_uuid: String,
|
||||
pub users_organizations_uuid: String
|
||||
}
|
||||
}
|
||||
|
||||
/// Local methods
|
||||
impl Group {
|
||||
pub fn new(organizations_uuid: String, name: String, access_all: bool, external_id: Option<String>) -> Self {
|
||||
let now = Utc::now().naive_utc();
|
||||
|
||||
let mut new_model = Self {
|
||||
uuid: crate::util::get_uuid(),
|
||||
organizations_uuid,
|
||||
name,
|
||||
access_all,
|
||||
external_id: None,
|
||||
creation_date: now,
|
||||
revision_date: now,
|
||||
};
|
||||
|
||||
new_model.set_external_id(external_id);
|
||||
|
||||
new_model
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> Value {
|
||||
use crate::util::format_date;
|
||||
|
||||
json!({
|
||||
"Id": self.uuid,
|
||||
"OrganizationId": self.organizations_uuid,
|
||||
"Name": self.name,
|
||||
"AccessAll": self.access_all,
|
||||
"ExternalId": self.external_id,
|
||||
"CreationDate": format_date(&self.creation_date),
|
||||
"RevisionDate": format_date(&self.revision_date)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_external_id(&mut self, external_id: Option<String>) {
|
||||
//Check if external id is empty. We don't want to have
|
||||
//empty strings in the database
|
||||
match external_id {
|
||||
Some(external_id) => {
|
||||
if external_id.is_empty() {
|
||||
self.external_id = None;
|
||||
} else {
|
||||
self.external_id = Some(external_id)
|
||||
}
|
||||
}
|
||||
None => self.external_id = None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_external_id(&self) -> Option<String> {
|
||||
self.external_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectionGroup {
|
||||
pub fn new(collections_uuid: String, groups_uuid: String, read_only: bool, hide_passwords: bool) -> Self {
|
||||
Self {
|
||||
collections_uuid,
|
||||
groups_uuid,
|
||||
read_only,
|
||||
hide_passwords,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupUser {
|
||||
pub fn new(groups_uuid: String, users_organizations_uuid: String) -> Self {
|
||||
Self {
|
||||
groups_uuid,
|
||||
users_organizations_uuid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
use super::{User, UserOrganization};
|
||||
|
||||
/// Database methods
|
||||
impl Group {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
self.revision_date = Utc::now().naive_utc();
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(groups::table)
|
||||
.values(GroupDb::to_db(self))
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
// Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(groups::table)
|
||||
.filter(groups::uuid.eq(&self.uuid))
|
||||
.set(GroupDb::to_db(self))
|
||||
.execute(conn)
|
||||
.map_res("Error saving group")
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}.map_res("Error saving group")
|
||||
}
|
||||
postgresql {
|
||||
let value = GroupDb::to_db(self);
|
||||
diesel::insert_into(groups::table)
|
||||
.values(&value)
|
||||
.on_conflict(groups::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.execute(conn)
|
||||
.map_res("Error saving group")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_organization(organizations_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
groups::table
|
||||
.filter(groups::organizations_uuid.eq(organizations_uuid))
|
||||
.load::<GroupDb>(conn)
|
||||
.expect("Error loading groups")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
groups::table
|
||||
.filter(groups::uuid.eq(uuid))
|
||||
.first::<GroupDb>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
//Returns all organizations the user has full access to
|
||||
pub async fn gather_user_organizations_full_access(user_uuid: &str, conn: &DbConn) -> Vec<String> {
|
||||
db_run! { conn: {
|
||||
groups_users::table
|
||||
.inner_join(users_organizations::table.on(
|
||||
users_organizations::uuid.eq(groups_users::users_organizations_uuid)
|
||||
))
|
||||
.inner_join(groups::table.on(
|
||||
groups::uuid.eq(groups_users::groups_uuid)
|
||||
))
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(groups::access_all.eq(true))
|
||||
.select(groups::organizations_uuid)
|
||||
.distinct()
|
||||
.load::<String>(conn)
|
||||
.expect("Error loading organization group full access information for user")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn is_in_full_access_group(user_uuid: &str, org_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::organizations_uuid.eq(org_uuid))
|
||||
.filter(groups::access_all.eq(true))
|
||||
.select(groups::access_all)
|
||||
.first::<bool>(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?;
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(groups::table.filter(groups::uuid.eq(&self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting group")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn update_revision(uuid: &str, conn: &DbConn) {
|
||||
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
|
||||
warn!("Failed to update revision for {}: {:#?}", uuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
|
||||
db_run! {conn: {
|
||||
crate::util::retry(|| {
|
||||
diesel::update(groups::table.filter(groups::uuid.eq(uuid)))
|
||||
.set(groups::revision_date.eq(date))
|
||||
.execute(conn)
|
||||
}, 10)
|
||||
.map_res("Error updating group revision")
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectionGroup {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await;
|
||||
for group_user in group_users {
|
||||
group_user.update_user_revision(conn).await;
|
||||
}
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(collections_groups::table)
|
||||
.values((
|
||||
collections_groups::collections_uuid.eq(&self.collections_uuid),
|
||||
collections_groups::groups_uuid.eq(&self.groups_uuid),
|
||||
collections_groups::read_only.eq(&self.read_only),
|
||||
collections_groups::hide_passwords.eq(&self.hide_passwords),
|
||||
))
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
// Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(collections_groups::table)
|
||||
.filter(collections_groups::collections_uuid.eq(&self.collections_uuid))
|
||||
.filter(collections_groups::groups_uuid.eq(&self.groups_uuid))
|
||||
.set((
|
||||
collections_groups::collections_uuid.eq(&self.collections_uuid),
|
||||
collections_groups::groups_uuid.eq(&self.groups_uuid),
|
||||
collections_groups::read_only.eq(&self.read_only),
|
||||
collections_groups::hide_passwords.eq(&self.hide_passwords),
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error adding group to collection")
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}.map_res("Error adding group to collection")
|
||||
}
|
||||
postgresql {
|
||||
diesel::insert_into(collections_groups::table)
|
||||
.values((
|
||||
collections_groups::collections_uuid.eq(&self.collections_uuid),
|
||||
collections_groups::groups_uuid.eq(&self.groups_uuid),
|
||||
collections_groups::read_only.eq(self.read_only),
|
||||
collections_groups::hide_passwords.eq(self.hide_passwords),
|
||||
))
|
||||
.on_conflict((collections_groups::collections_uuid, collections_groups::groups_uuid))
|
||||
.do_update()
|
||||
.set((
|
||||
collections_groups::read_only.eq(self.read_only),
|
||||
collections_groups::hide_passwords.eq(self.hide_passwords),
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error adding group to collection")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_group(group_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
collections_groups::table
|
||||
.filter(collections_groups::groups_uuid.eq(group_uuid))
|
||||
.load::<CollectionGroupDb>(conn)
|
||||
.expect("Error loading collection groups")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
collections_groups::table
|
||||
.inner_join(groups_users::table.on(
|
||||
groups_users::groups_uuid.eq(collections_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(collections_groups::all_columns)
|
||||
.load::<CollectionGroupDb>(conn)
|
||||
.expect("Error loading user collection groups")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
collections_groups::table
|
||||
.filter(collections_groups::collections_uuid.eq(collection_uuid))
|
||||
.select(collections_groups::all_columns)
|
||||
.load::<CollectionGroupDb>(conn)
|
||||
.expect("Error loading 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 {
|
||||
group_user.update_user_revision(conn).await;
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(collections_groups::table)
|
||||
.filter(collections_groups::collections_uuid.eq(&self.collections_uuid))
|
||||
.filter(collections_groups::groups_uuid.eq(&self.groups_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting collection group")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_group(group_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||
let group_users = GroupUser::find_by_group(group_uuid, conn).await;
|
||||
for group_user in group_users {
|
||||
group_user.update_user_revision(conn).await;
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(collections_groups::table)
|
||||
.filter(collections_groups::groups_uuid.eq(group_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting collection group")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &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;
|
||||
for group_user in group_users {
|
||||
group_user.update_user_revision(conn).await;
|
||||
}
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(collections_groups::table)
|
||||
.filter(collections_groups::collections_uuid.eq(collection_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting collection group")
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupUser {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
self.update_user_revision(conn).await;
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(groups_users::table)
|
||||
.values((
|
||||
groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid),
|
||||
groups_users::groups_uuid.eq(&self.groups_uuid),
|
||||
))
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
// Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(groups_users::table)
|
||||
.filter(groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid))
|
||||
.filter(groups_users::groups_uuid.eq(&self.groups_uuid))
|
||||
.set((
|
||||
groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid),
|
||||
groups_users::groups_uuid.eq(&self.groups_uuid),
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error adding user to group")
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}.map_res("Error adding user to group")
|
||||
}
|
||||
postgresql {
|
||||
diesel::insert_into(groups_users::table)
|
||||
.values((
|
||||
groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid),
|
||||
groups_users::groups_uuid.eq(&self.groups_uuid),
|
||||
))
|
||||
.on_conflict((groups_users::users_organizations_uuid, groups_users::groups_uuid))
|
||||
.do_update()
|
||||
.set((
|
||||
groups_users::users_organizations_uuid.eq(&self.users_organizations_uuid),
|
||||
groups_users::groups_uuid.eq(&self.groups_uuid),
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error adding user to group")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_group(group_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
groups_users::table
|
||||
.filter(groups_users::groups_uuid.eq(group_uuid))
|
||||
.load::<GroupUserDb>(conn)
|
||||
.expect("Error loading group users")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(users_organizations_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
groups_users::table
|
||||
.filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid))
|
||||
.load::<GroupUserDb>(conn)
|
||||
.expect("Error loading groups for user")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn update_user_revision(&self, conn: &DbConn) {
|
||||
match UserOrganization::find_by_uuid(&self.users_organizations_uuid, conn).await {
|
||||
Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await,
|
||||
None => warn!("User could not be found!"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_by_group_id_and_user_id(
|
||||
group_uuid: &str,
|
||||
users_organizations_uuid: &str,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
match UserOrganization::find_by_uuid(users_organizations_uuid, conn).await {
|
||||
Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await,
|
||||
None => warn!("User could not be found!"),
|
||||
};
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(groups_users::table)
|
||||
.filter(groups_users::groups_uuid.eq(group_uuid))
|
||||
.filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting group users")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_group(group_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||
let group_users = GroupUser::find_by_group(group_uuid, conn).await;
|
||||
for group_user in group_users {
|
||||
group_user.update_user_revision(conn).await;
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(groups_users::table)
|
||||
.filter(groups_users::groups_uuid.eq(group_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting group users")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(users_organizations_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||
match UserOrganization::find_by_uuid(users_organizations_uuid, conn).await {
|
||||
Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await,
|
||||
None => warn!("User could not be found!"),
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(groups_users::table)
|
||||
.filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting user groups")
|
||||
}}
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@ mod device;
|
||||
mod emergency_access;
|
||||
mod favorite;
|
||||
mod folder;
|
||||
mod group;
|
||||
mod org_policy;
|
||||
mod organization;
|
||||
mod send;
|
||||
@@ -19,6 +20,7 @@ pub use self::device::Device;
|
||||
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType};
|
||||
pub use self::favorite::Favorite;
|
||||
pub use self::folder::{Folder, FolderCipher};
|
||||
pub use self::group::{CollectionGroup, Group, GroupUser};
|
||||
pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyType};
|
||||
pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
|
||||
pub use self::send::{Send, SendType};
|
||||
|
@@ -2,7 +2,7 @@ use num_traits::FromPrimitive;
|
||||
use serde_json::Value;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use super::{CollectionUser, OrgPolicy, OrgPolicyType, User};
|
||||
use super::{CollectionUser, GroupUser, OrgPolicy, OrgPolicyType, User};
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
@@ -148,7 +148,7 @@ impl Organization {
|
||||
"Use2fa": true,
|
||||
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
|
||||
"UseEvents": false, // Not supported
|
||||
"UseGroups": false, // Not supported
|
||||
"UseGroups": true,
|
||||
"UseTotp": true,
|
||||
"UsePolicies": true,
|
||||
// "UseScim": false, // Not supported (Not AGPLv3 Licensed)
|
||||
@@ -300,7 +300,7 @@ impl UserOrganization {
|
||||
"Use2fa": true,
|
||||
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
|
||||
"UseEvents": false, // Not supported
|
||||
"UseGroups": false, // Not supported
|
||||
"UseGroups": true,
|
||||
"UseTotp": true,
|
||||
// "UseScim": false, // Not supported (Not AGPLv3 Licensed)
|
||||
"UsePolicies": true,
|
||||
@@ -459,6 +459,7 @@ impl UserOrganization {
|
||||
User::update_uuid_revision(&self.user_uuid, conn).await;
|
||||
|
||||
CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?;
|
||||
GroupUser::delete_all_by_user(&self.uuid, conn).await?;
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid)))
|
||||
|
Reference in New Issue
Block a user