mirror of
https://github.com/dani-garcia/vaultwarden.wiki.git
synced 2026-07-22 19:33:34 +00:00
Switch to Edition 2024, more clippy lints, and less macro calls (#7200)
* Update to Rust 2024 Edition Updated to the Rust 2024 Edition and added and fixed several lint checks. This is a large change which, because of the extra lints, added some possible fixes for issues. Signed-off-by: BlackDex <black.dex@gmail.com> * Reorder and merge imports Signed-off-by: BlackDex <black.dex@gmail.com> * Remove "db_run!" macro calls where possible Signed-off-by: BlackDex <black.dex@gmail.com> --------- Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
committed by
GitHub
parent
22f5e0496c
commit
1ba2c6a26c
+26
-29
@@ -6,25 +6,23 @@ use std::{
|
||||
};
|
||||
|
||||
use diesel::{
|
||||
Connection, RunQueryDsl,
|
||||
connection::SimpleConnection,
|
||||
r2d2::{CustomizeConnection, Pool, PooledConnection},
|
||||
Connection, RunQueryDsl,
|
||||
};
|
||||
|
||||
use rocket::{
|
||||
Request,
|
||||
http::Status,
|
||||
request::{FromRequest, Outcome},
|
||||
Request,
|
||||
};
|
||||
|
||||
use tokio::{
|
||||
sync::{Mutex, OwnedSemaphorePermit, Semaphore},
|
||||
time::timeout,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{Error, MapResult},
|
||||
CONFIG,
|
||||
error::{Error, MapResult},
|
||||
};
|
||||
|
||||
// These changes are based on Rocket 0.5-rc wrapper of Diesel: https://github.com/SergioBenitez/Rocket/blob/v0.5-rc/contrib/sync_db_pools
|
||||
@@ -62,7 +60,7 @@ pub struct DbConnManager {
|
||||
impl DbConnManager {
|
||||
pub fn new(database_url: &str) -> Self {
|
||||
Self {
|
||||
database_url: database_url.to_string(),
|
||||
database_url: database_url.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +222,7 @@ impl DbPool {
|
||||
|
||||
// Set a global to determine the database more easily throughout the rest of the code
|
||||
if ACTIVE_DB_TYPE.set(conn_type).is_err() {
|
||||
error!("Tried to set the active database connection type more than once.")
|
||||
error!("Tried to set the active database connection type more than once.");
|
||||
}
|
||||
|
||||
Ok(DbPool {
|
||||
@@ -279,34 +277,33 @@ impl DbConnType {
|
||||
|
||||
#[cfg(not(sqlite))]
|
||||
err!("`DATABASE_URL` is a SQLite URL, but the 'sqlite' feature is not enabled")
|
||||
}
|
||||
|
||||
// No recognized scheme — assume legacy bare-path SQLite, but the database file must already exist.
|
||||
// This prevents misconfigured URLs (typos, quoted strings) from silently creating a new empty SQLite database.
|
||||
} else {
|
||||
#[cfg(sqlite)]
|
||||
{
|
||||
if std::path::Path::new(url).exists() {
|
||||
return Ok(DbConnType::Sqlite);
|
||||
}
|
||||
err!(format!(
|
||||
"`DATABASE_URL` does not match any known database scheme (mysql://, postgresql://, sqlite://) \
|
||||
and no existing SQLite database was found at '{url}'. \
|
||||
If you intend to use SQLite, use an explicit `sqlite://` scheme in your `DATABASE_URL`. \
|
||||
Otherwise, check your DATABASE_URL for typos or quoting issues."
|
||||
))
|
||||
#[cfg(sqlite)]
|
||||
{
|
||||
if std::path::Path::new(url).exists() {
|
||||
return Ok(DbConnType::Sqlite);
|
||||
}
|
||||
|
||||
#[cfg(not(sqlite))]
|
||||
err!("`DATABASE_URL` does not match any known database scheme (mysql://, postgresql://, sqlite://)")
|
||||
err!(format!(
|
||||
"`DATABASE_URL` does not match any known database scheme (mysql://, postgresql://, sqlite://) \
|
||||
and no existing SQLite database was found at '{url}'. \
|
||||
If you intend to use SQLite, use an explicit `sqlite://` scheme in your `DATABASE_URL`. \
|
||||
Otherwise, check your DATABASE_URL for typos or quoting issues."
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(sqlite))]
|
||||
err!("`DATABASE_URL` does not match any known database scheme (mysql://, postgresql://, sqlite://)")
|
||||
}
|
||||
|
||||
pub fn get_init_stmts(&self) -> String {
|
||||
let init_stmts = CONFIG.database_conn_init();
|
||||
if !init_stmts.is_empty() {
|
||||
init_stmts
|
||||
} else {
|
||||
if init_stmts.is_empty() {
|
||||
self.default_init_stmts()
|
||||
} else {
|
||||
init_stmts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +314,7 @@ impl DbConnType {
|
||||
#[cfg(postgresql)]
|
||||
Self::Postgresql => String::new(),
|
||||
#[cfg(sqlite)]
|
||||
Self::Sqlite => "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;".to_string(),
|
||||
Self::Sqlite => "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,7 +405,7 @@ pub fn backup_sqlite() -> Result<String, Error> {
|
||||
use diesel::Connection;
|
||||
|
||||
let db_url = CONFIG.database_url();
|
||||
if DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::Sqlite).unwrap_or(false) {
|
||||
if DbConnType::from_url(&CONFIG.database_url()).is_ok_and(|t| t == DbConnType::Sqlite) {
|
||||
// Strip the sqlite:// prefix if present to get the raw file path
|
||||
let file_path = db_url.strip_prefix("sqlite://").unwrap_or(&db_url);
|
||||
// Open a read-only connection for the backup
|
||||
@@ -443,12 +440,12 @@ pub async fn get_sql_server_version(conn: &DbConn) -> String {
|
||||
postgresql,mysql {
|
||||
diesel::select(diesel::dsl::sql::<diesel::sql_types::Text>("version();"))
|
||||
.get_result::<String>(conn)
|
||||
.unwrap_or_else(|_| "Unknown".to_string())
|
||||
.unwrap_or_else(|_| "Unknown".to_owned())
|
||||
}
|
||||
sqlite {
|
||||
diesel::select(diesel::dsl::sql::<diesel::sql_types::Text>("sqlite_version();"))
|
||||
.get_result::<String>(conn)
|
||||
.unwrap_or_else(|_| "Unknown".to_string())
|
||||
.unwrap_or_else(|_| "Unknown".to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-14
@@ -1,11 +1,13 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
db::{DbConn, schema::archives},
|
||||
error::MapResult,
|
||||
};
|
||||
|
||||
use super::{CipherId, User, UserId};
|
||||
use crate::api::EmptyResult;
|
||||
use crate::db::schema::archives;
|
||||
use crate::db::DbConn;
|
||||
use crate::error::MapResult;
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = archives)]
|
||||
@@ -19,13 +21,15 @@ pub struct Archive {
|
||||
impl Archive {
|
||||
// Returns the date the specified cipher was archived
|
||||
pub async fn get_archived_at(cipher_uuid: &CipherId, user_uuid: &UserId, conn: &DbConn) -> Option<NaiveDateTime> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
archives::table
|
||||
.filter(archives::cipher_uuid.eq(cipher_uuid))
|
||||
.filter(archives::user_uuid.eq(user_uuid))
|
||||
.select(archives::archived_at)
|
||||
.first::<NaiveDateTime>(conn).ok()
|
||||
}}
|
||||
.first::<NaiveDateTime>(conn)
|
||||
.ok()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// Saves (inserts or updates) an archive record with the provided timestamp
|
||||
@@ -66,26 +70,26 @@ impl Archive {
|
||||
// Deletes an archive record for a specific cipher
|
||||
pub async fn delete_by_cipher(user_uuid: &UserId, cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(
|
||||
archives::table
|
||||
.filter(archives::user_uuid.eq(user_uuid))
|
||||
.filter(archives::cipher_uuid.eq(cipher_uuid))
|
||||
archives::table.filter(archives::user_uuid.eq(user_uuid)).filter(archives::cipher_uuid.eq(cipher_uuid)),
|
||||
)
|
||||
.execute(conn)
|
||||
.map_res("Error deleting archive")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return a vec with (cipher_uuid, archived_at)
|
||||
/// This is used during a full sync so we only need one query for all archive matches
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<(CipherId, NaiveDateTime)> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
archives::table
|
||||
.filter(archives::user_uuid.eq(user_uuid))
|
||||
.select((archives::cipher_uuid, archives::archived_at))
|
||||
.load::<(CipherId, NaiveDateTime)>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
+42
-35
@@ -1,13 +1,24 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use bigdecimal::{BigDecimal, ToPrimitive};
|
||||
use derive_more::{AsRef, Deref, Display};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::EmptyResult,
|
||||
auth::{encode_jwt, generate_file_download_claims},
|
||||
config::PathType,
|
||||
db::{
|
||||
DbConn,
|
||||
schema::{attachments, ciphers},
|
||||
},
|
||||
error::MapResult,
|
||||
};
|
||||
use macros::IdFromParam;
|
||||
|
||||
use super::{CipherId, OrganizationId, UserId};
|
||||
use crate::db::schema::{attachments, ciphers};
|
||||
use crate::{config::PathType, CONFIG};
|
||||
use macros::IdFromParam;
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = attachments)]
|
||||
@@ -67,12 +78,6 @@ impl Attachment {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::auth::{encode_jwt, generate_file_download_claims};
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Attachment {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
@@ -107,15 +112,15 @@ impl Attachment {
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
crate::util::retry(||
|
||||
diesel::delete(attachments::table.filter(attachments::id.eq(&self.id)))
|
||||
.execute(conn),
|
||||
conn.run(move |conn| {
|
||||
crate::util::retry(
|
||||
|| diesel::delete(attachments::table.filter(attachments::id.eq(&self.id))).execute(conn),
|
||||
10,
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_res("Error deleting attachment")
|
||||
}}?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
let operator = CONFIG.opendal_operator_for_path_type(&PathType::Attachments)?;
|
||||
let file_path = self.get_file_path();
|
||||
@@ -139,25 +144,22 @@ impl Attachment {
|
||||
}
|
||||
|
||||
pub async fn find_by_id(id: &AttachmentId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
attachments::table
|
||||
.filter(attachments::id.eq(id.to_lowercase()))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| attachments::table.filter(attachments::id.eq(id.to_lowercase())).first::<Self>(conn).ok())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_cipher(cipher_uuid: &CipherId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
attachments::table
|
||||
.filter(attachments::cipher_uuid.eq(cipher_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading attachments")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn size_by_user(user_uuid: &UserId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
let result: Option<BigDecimal> = attachments::table
|
||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||
@@ -168,24 +170,26 @@ impl Attachment {
|
||||
match result.map(|r| r.to_i64()) {
|
||||
Some(Some(r)) => r,
|
||||
Some(None) => i64::MAX,
|
||||
None => 0
|
||||
None => 0,
|
||||
}
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_by_user(user_uuid: &UserId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
attachments::table
|
||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||
.count()
|
||||
.first(conn)
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn size_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
let result: Option<BigDecimal> = attachments::table
|
||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||
@@ -196,20 +200,22 @@ impl Attachment {
|
||||
match result.map(|r| r.to_i64()) {
|
||||
Some(Some(r)) => r,
|
||||
Some(None) => i64::MAX,
|
||||
None => 0
|
||||
None => 0,
|
||||
}
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
attachments::table
|
||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||
.count()
|
||||
.first(conn)
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// This will return all attachments linked to the user or org
|
||||
@@ -220,7 +226,7 @@ impl Attachment {
|
||||
org_uuids: &Vec<OrganizationId>,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
attachments::table
|
||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||
@@ -228,7 +234,8 @@ impl Attachment {
|
||||
.select(attachments::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading attachments")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
use super::{DeviceId, OrganizationId, UserId};
|
||||
use crate::db::schema::auth_requests;
|
||||
use crate::{crypto::ct_eq, util::format_date};
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use macros::UuidFromParam;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
crypto::ct_eq,
|
||||
db::{DbConn, schema::auth_requests},
|
||||
error::MapResult,
|
||||
util::format_date,
|
||||
};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
use super::{DeviceId, OrganizationId, UserId};
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
|
||||
#[diesel(table_name = auth_requests)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
@@ -74,11 +81,6 @@ impl AuthRequest {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
impl AuthRequest {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
@@ -112,31 +114,28 @@ impl AuthRequest {
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &AuthRequestId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::uuid.eq(uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| auth_requests::table.filter(auth_requests::uuid.eq(uuid)).first::<Self>(conn).ok()).await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &AuthRequestId, user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::uuid.eq(uuid))
|
||||
.filter(auth_requests::user_uuid.eq(user_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::user_uuid.eq(user_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading auth_requests")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_requested_device(
|
||||
@@ -144,7 +143,7 @@ impl AuthRequest {
|
||||
device_uuid: &DeviceId,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::user_uuid.eq(user_uuid))
|
||||
.filter(auth_requests::request_device_identifier.eq(device_uuid))
|
||||
@@ -152,24 +151,27 @@ impl AuthRequest {
|
||||
.order_by(auth_requests::creation_date.desc())
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_created_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::creation_date.lt(dt))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading auth_requests")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(auth_requests::table.filter(auth_requests::uuid.eq(&self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting auth request")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn check_access_code(&self, access_code: &str) -> bool {
|
||||
|
||||
+345
-339
@@ -1,22 +1,32 @@
|
||||
use crate::db::schema::{
|
||||
ciphers, ciphers_collections, collections, collections_groups, folders, folders_ciphers, groups, groups_users,
|
||||
users_collections, users_organizations,
|
||||
};
|
||||
use crate::util::LowerCase;
|
||||
use crate::CONFIG;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use chrono::{NaiveDateTime, TimeDelta, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::{
|
||||
EmptyResult,
|
||||
core::{CipherData, CipherSyncData, CipherSyncType},
|
||||
},
|
||||
db::{
|
||||
DbConn,
|
||||
schema::{
|
||||
ciphers, ciphers_collections, collections, collections_groups, folders, folders_ciphers, groups,
|
||||
groups_users, users_collections, users_organizations,
|
||||
},
|
||||
},
|
||||
error::MapResult,
|
||||
util::LowerCase,
|
||||
};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
use super::{
|
||||
Archive, Attachment, CollectionCipher, CollectionId, Favorite, FolderCipher, FolderId, Group, Membership,
|
||||
MembershipStatus, MembershipType, OrganizationId, User, UserId,
|
||||
};
|
||||
use crate::api::core::{CipherData, CipherSyncData, CipherSyncType};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = ciphers)]
|
||||
@@ -91,27 +101,27 @@ impl Cipher {
|
||||
format!("The field Notes exceeds the maximum encrypted value length of {max_note_size} characters.");
|
||||
for (index, cipher) in cipher_data.iter().enumerate() {
|
||||
// Validate the note size and if it is exceeded return a warning
|
||||
if let Some(note) = &cipher.notes {
|
||||
if note.len() > max_note_size {
|
||||
validation_errors
|
||||
.insert(format!("Ciphers[{index}].Notes"), serde_json::to_value([&max_note_size_msg]).unwrap());
|
||||
}
|
||||
if let Some(note) = &cipher.notes
|
||||
&& note.len() > max_note_size
|
||||
{
|
||||
validation_errors
|
||||
.insert(format!("Ciphers[{index}].Notes"), serde_json::to_value([&max_note_size_msg]).unwrap());
|
||||
}
|
||||
|
||||
// Validate the password history if it contains `null` values and if so, return a warning
|
||||
if let Some(Value::Array(password_history)) = &cipher.password_history {
|
||||
for pwh in password_history {
|
||||
if let Value::Object(pwo) = pwh {
|
||||
if pwo.get("password").is_some_and(|p| !p.is_string()) {
|
||||
validation_errors.insert(
|
||||
format!("Ciphers[{index}].Notes"),
|
||||
serde_json::to_value([
|
||||
"The password history contains a `null` value. Only strings are allowed.",
|
||||
])
|
||||
.unwrap(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
if let Value::Object(pwo) = pwh
|
||||
&& pwo.get("password").is_some_and(|p| !p.is_string())
|
||||
{
|
||||
validation_errors.insert(
|
||||
format!("Ciphers[{index}].Notes"),
|
||||
serde_json::to_value([
|
||||
"The password history contains a `null` value. Only strings are allowed.",
|
||||
])
|
||||
.unwrap(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,17 +134,12 @@ impl Cipher {
|
||||
"object": "error"
|
||||
});
|
||||
err_json!(err_json, "Import validation errors")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Cipher {
|
||||
pub async fn to_json(
|
||||
@@ -149,14 +154,14 @@ impl Cipher {
|
||||
|
||||
let mut attachments_json: Value = Value::Null;
|
||||
if let Some(cipher_sync_data) = cipher_sync_data {
|
||||
if let Some(attachments) = cipher_sync_data.cipher_attachments.get(&self.uuid) {
|
||||
if !attachments.is_empty() {
|
||||
let mut attachments_json_vec = vec![];
|
||||
for attachment in attachments {
|
||||
attachments_json_vec.push(attachment.to_json(host).await?);
|
||||
}
|
||||
attachments_json = Value::Array(attachments_json_vec);
|
||||
if let Some(attachments) = cipher_sync_data.cipher_attachments.get(&self.uuid)
|
||||
&& !attachments.is_empty()
|
||||
{
|
||||
let mut attachments_json_vec = vec![];
|
||||
for attachment in attachments {
|
||||
attachments_json_vec.push(attachment.to_json(host).await?);
|
||||
}
|
||||
attachments_json = Value::Array(attachments_json_vec);
|
||||
}
|
||||
} else {
|
||||
let attachments = Attachment::find_by_cipher(&self.uuid, conn).await;
|
||||
@@ -172,12 +177,11 @@ impl Cipher {
|
||||
// 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.
|
||||
let (read_only, hide_passwords, _) = if sync_type == CipherSyncType::User {
|
||||
match self.get_access_restrictions(user_uuid, cipher_sync_data, conn).await {
|
||||
Some((ro, hp, mn)) => (ro, hp, mn),
|
||||
None => {
|
||||
error!("Cipher ownership assertion failure");
|
||||
(true, true, false)
|
||||
}
|
||||
if let Some((ro, hp, mn)) = self.get_access_restrictions(user_uuid, cipher_sync_data, conn).await {
|
||||
(ro, hp, mn)
|
||||
} else {
|
||||
error!("Cipher ownership assertion failure");
|
||||
(true, true, false)
|
||||
}
|
||||
} else {
|
||||
(false, false, false)
|
||||
@@ -231,15 +235,14 @@ impl Cipher {
|
||||
Some(p) if p.is_string() => Some(d.data),
|
||||
_ => None,
|
||||
})
|
||||
.map(|mut d| match d.get("lastUsedDate").and_then(|l| l.as_str()) {
|
||||
Some(l) => {
|
||||
d["lastUsedDate"] = json!(validate_and_format_date(l));
|
||||
d
|
||||
}
|
||||
_ => {
|
||||
d["lastUsedDate"] = json!("1970-01-01T00:00:00.000000Z");
|
||||
d
|
||||
}
|
||||
.map(|mut d| {
|
||||
let lud = if let Some(l) = d.get("lastUsedDate").and_then(|l| l.as_str()) {
|
||||
validate_and_format_date(l)
|
||||
} else {
|
||||
"1970-01-01T00:00:00.000000Z".to_owned()
|
||||
};
|
||||
d["lastUsedDate"] = json!(lud);
|
||||
d
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
@@ -247,32 +250,30 @@ impl Cipher {
|
||||
|
||||
// Get the type_data or a default to an empty json object '{}'.
|
||||
// If not passing an empty object, mobile clients will crash.
|
||||
let mut type_data_json =
|
||||
serde_json::from_str::<LowerCase<Value>>(&self.data).map(|d| d.data).unwrap_or_else(|_| {
|
||||
warn!("Error parsing data field for {}", self.uuid);
|
||||
Value::Object(serde_json::Map::new())
|
||||
});
|
||||
let mut type_data_json = serde_json::from_str::<LowerCase<Value>>(&self.data)
|
||||
.inspect_err(|_| warn!("Error parsing data field for {}", self.uuid))
|
||||
.map_or_else(|_| Value::Object(serde_json::Map::new()), |d| d.data);
|
||||
|
||||
// NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream
|
||||
// Set the first element of the Uris array as Uri, this is needed several (mobile) clients.
|
||||
if self.atype == 1 {
|
||||
// Upstream always has an `uri` key/value
|
||||
type_data_json["uri"] = Value::Null;
|
||||
if let Some(uris) = type_data_json["uris"].as_array_mut() {
|
||||
if !uris.is_empty() {
|
||||
// Fix uri match values first, they are only allowed to be a number or null
|
||||
// If it is a string, convert it to an int or null if that fails
|
||||
for uri in &mut *uris {
|
||||
if uri["match"].is_string() {
|
||||
let match_value = match uri["match"].as_str().unwrap_or_default().parse::<u8>() {
|
||||
Ok(n) => json!(n),
|
||||
_ => Value::Null,
|
||||
};
|
||||
uri["match"] = match_value;
|
||||
}
|
||||
if let Some(uris) = type_data_json["uris"].as_array_mut()
|
||||
&& !uris.is_empty()
|
||||
{
|
||||
// Fix uri match values first, they are only allowed to be a number or null
|
||||
// If it is a string, convert it to an int or null if that fails
|
||||
for uri in &mut *uris {
|
||||
if uri["match"].is_string() {
|
||||
let match_value = match uri["match"].as_str().unwrap_or_default().parse::<u8>() {
|
||||
Ok(n) => json!(n),
|
||||
_ => Value::Null,
|
||||
};
|
||||
uri["match"] = match_value;
|
||||
}
|
||||
type_data_json["uri"] = uris[0]["uri"].clone();
|
||||
}
|
||||
type_data_json["uri"] = uris[0]["uri"].clone();
|
||||
}
|
||||
|
||||
// Check if `passwordRevisionDate` is a valid date, else convert it
|
||||
@@ -285,7 +286,7 @@ impl Cipher {
|
||||
// This breaks at least the native mobile clients
|
||||
if self.atype == 2 {
|
||||
match type_data_json {
|
||||
Value::Object(ref t) if t.get("type").is_some_and(|t| t.is_number()) => {}
|
||||
Value::Object(ref t) if t.get("type").is_some_and(Value::is_number) => {}
|
||||
_ => {
|
||||
type_data_json = json!({"type": 0});
|
||||
}
|
||||
@@ -297,9 +298,9 @@ impl Cipher {
|
||||
// The only way to fix this is by setting type_data_json to `null`
|
||||
// Opening this ssh-key in the mobile client will probably crash the client, but you can edit, save and afterwards delete it
|
||||
if self.atype == 5
|
||||
&& (type_data_json["keyFingerprint"].as_str().is_none_or(|v| v.is_empty())
|
||||
|| type_data_json["privateKey"].as_str().is_none_or(|v| v.is_empty())
|
||||
|| type_data_json["publicKey"].as_str().is_none_or(|v| v.is_empty()))
|
||||
&& (type_data_json["keyFingerprint"].as_str().is_none_or(str::is_empty)
|
||||
|| type_data_json["privateKey"].as_str().is_none_or(str::is_empty)
|
||||
|| type_data_json["publicKey"].as_str().is_none_or(str::is_empty))
|
||||
{
|
||||
warn!("Error parsing ssh-key, mandatory fields are invalid for {}", self.uuid);
|
||||
type_data_json = Value::Null;
|
||||
@@ -415,7 +416,7 @@ impl Cipher {
|
||||
match self.user_uuid {
|
||||
Some(ref user_uuid) => {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
user_uuids.push(user_uuid.clone())
|
||||
user_uuids.push(user_uuid.clone());
|
||||
}
|
||||
None => {
|
||||
// Belongs to Organization, need to update affected users
|
||||
@@ -430,11 +431,11 @@ impl Cipher {
|
||||
}
|
||||
for member in collection_users {
|
||||
User::update_uuid_revision(&member.user_uuid, conn).await;
|
||||
user_uuids.push(member.user_uuid.clone())
|
||||
user_uuids.push(member.user_uuid.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
user_uuids
|
||||
}
|
||||
|
||||
@@ -480,11 +481,12 @@ impl Cipher {
|
||||
Attachment::delete_all_by_cipher(&self.uuid, conn).await?;
|
||||
Favorite::delete_all_by_cipher(&self.uuid, conn).await?;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(ciphers::table.filter(ciphers::uuid.eq(&self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting cipher")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -531,9 +533,10 @@ impl Cipher {
|
||||
|
||||
// Remove from folder
|
||||
(Some(old_folder), None) => {
|
||||
match FolderCipher::find_by_folder_and_cipher(&old_folder, &self.uuid, conn).await {
|
||||
Some(old_folder) => old_folder.delete(conn).await,
|
||||
None => err!("Couldn't move from previous folder"),
|
||||
if let Some(old_folder) = FolderCipher::find_by_folder_and_cipher(&old_folder, &self.uuid, conn).await {
|
||||
old_folder.delete(conn).await
|
||||
} else {
|
||||
err!("Couldn't move from previous folder")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,9 +587,8 @@ impl Cipher {
|
||||
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.contains(org_uuid);
|
||||
} else {
|
||||
return Group::is_in_full_access_group(user_uuid, org_uuid, conn).await;
|
||||
}
|
||||
return Group::is_in_full_access_group(user_uuid, org_uuid, conn).await;
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -628,10 +630,10 @@ impl Cipher {
|
||||
rows
|
||||
} else {
|
||||
let user_permissions = self.get_user_collections_access_flags(user_uuid, conn).await;
|
||||
if !user_permissions.is_empty() {
|
||||
user_permissions
|
||||
} else {
|
||||
if user_permissions.is_empty() {
|
||||
self.get_group_collections_access_flags(user_uuid, conn).await
|
||||
} else {
|
||||
user_permissions
|
||||
}
|
||||
};
|
||||
|
||||
@@ -657,7 +659,7 @@ impl Cipher {
|
||||
let mut read_only = true;
|
||||
let mut hide_passwords = true;
|
||||
let mut manage = false;
|
||||
for (ro, hp, mn) in rows.iter() {
|
||||
for (ro, hp, mn) in &rows {
|
||||
read_only &= ro;
|
||||
hide_passwords &= hp;
|
||||
manage |= mn;
|
||||
@@ -667,51 +669,51 @@ impl Cipher {
|
||||
}
|
||||
|
||||
async fn get_user_collections_access_flags(&self, user_uuid: &UserId, conn: &DbConn) -> Vec<(bool, bool, bool)> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
// Check whether this cipher is in any collections accessible to the
|
||||
// user. If so, retrieve the access flags for each collection.
|
||||
ciphers::table
|
||||
.filter(ciphers::uuid.eq(&self.uuid))
|
||||
.inner_join(ciphers_collections::table.on(
|
||||
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
||||
))
|
||||
.inner_join(users_collections::table.on(
|
||||
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid))
|
||||
))
|
||||
.inner_join(ciphers_collections::table.on(ciphers::uuid.eq(ciphers_collections::cipher_uuid)))
|
||||
.inner_join(
|
||||
users_collections::table.on(ciphers_collections::collection_uuid
|
||||
.eq(users_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid))),
|
||||
)
|
||||
.select((users_collections::read_only, users_collections::hide_passwords, users_collections::manage))
|
||||
.load::<(bool, bool, bool)>(conn)
|
||||
.expect("Error getting user access restrictions")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_group_collections_access_flags(&self, user_uuid: &UserId, conn: &DbConn) -> Vec<(bool, bool, bool)> {
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
return Vec::new();
|
||||
}
|
||||
db_run! { conn: {
|
||||
conn.run(move |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)
|
||||
))
|
||||
.inner_join(groups::table.on(groups::uuid.eq(collections_groups::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_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)),
|
||||
)
|
||||
.inner_join(
|
||||
groups::table.on(groups::uuid
|
||||
.eq(collections_groups::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||
)
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.select((collections_groups::read_only, collections_groups::hide_passwords, collections_groups::manage))
|
||||
.load::<(bool, bool, bool)>(conn)
|
||||
.expect("Error getting group access restrictions")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn is_write_accessible_to_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
@@ -760,7 +762,7 @@ impl Cipher {
|
||||
}
|
||||
|
||||
pub async fn get_folder_uuid(&self, user_uuid: &UserId, conn: &DbConn) -> Option<FolderId> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
folders_ciphers::table
|
||||
.inner_join(folders::table)
|
||||
.filter(folders::user_uuid.eq(&user_uuid))
|
||||
@@ -768,16 +770,12 @@ impl Cipher {
|
||||
.select(folders_ciphers::folder_uuid)
|
||||
.first::<FolderId>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &CipherId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(ciphers::uuid.eq(uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| ciphers::table.filter(ciphers::uuid.eq(uuid)).first::<Self>(conn).ok()).await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_org(
|
||||
@@ -785,13 +783,14 @@ impl Cipher {
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
ciphers::table
|
||||
.filter(ciphers::uuid.eq(cipher_uuid))
|
||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// Find all ciphers accessible or visible to the specified user.
|
||||
@@ -813,32 +812,35 @@ impl Cipher {
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
let mut query = ciphers::table
|
||||
.left_join(ciphers_collections::table.on(
|
||||
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
||||
))
|
||||
.left_join(users_organizations::table.on(
|
||||
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())
|
||||
.left_join(ciphers_collections::table.on(ciphers::uuid.eq(ciphers_collections::cipher_uuid)))
|
||||
.left_join(
|
||||
users_organizations::table.on(ciphers::organization_uuid
|
||||
.eq(users_organizations::org_uuid.nullable())
|
||||
.and(users_organizations::user_uuid.eq(user_uuid))
|
||||
.and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
))
|
||||
.left_join(users_collections::table.on(
|
||||
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
||||
.and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))),
|
||||
)
|
||||
.left_join(
|
||||
users_collections::table.on(ciphers_collections::collection_uuid
|
||||
.eq(users_collections::collection_uuid)
|
||||
// Ensure that users_collections::user_uuid is NULL for unconfirmed users.
|
||||
.and(users_organizations::user_uuid.eq(users_collections::user_uuid))
|
||||
))
|
||||
.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)
|
||||
// Ensure that group and membership belong to the same org
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))
|
||||
))
|
||||
.left_join(collections_groups::table.on(
|
||||
collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid)
|
||||
.and(collections_groups::groups_uuid.eq(groups::uuid))
|
||||
))
|
||||
.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)
|
||||
// Ensure that group and membership belong to the same org
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_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
|
||||
@@ -848,39 +850,34 @@ impl Cipher {
|
||||
|
||||
if !visible_only {
|
||||
query = query.or_filter(
|
||||
users_organizations::atype.le(MembershipType::Admin as i32) // Org admin/owner
|
||||
users_organizations::atype.le(MembershipType::Admin as i32), // Org admin/owner
|
||||
);
|
||||
}
|
||||
|
||||
// Only filter for one specific cipher
|
||||
if !cipher_uuids.is_empty() {
|
||||
query = query.filter(
|
||||
ciphers::uuid.eq_any(cipher_uuids)
|
||||
);
|
||||
query = query.filter(ciphers::uuid.eq_any(cipher_uuids));
|
||||
}
|
||||
|
||||
query
|
||||
.select(ciphers::all_columns)
|
||||
.distinct()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
query.select(ciphers::all_columns).distinct().load::<Self>(conn).expect("Error loading ciphers")
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
let mut query = ciphers::table
|
||||
.left_join(ciphers_collections::table.on(
|
||||
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
||||
))
|
||||
.left_join(users_organizations::table.on(
|
||||
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())
|
||||
.left_join(ciphers_collections::table.on(ciphers::uuid.eq(ciphers_collections::cipher_uuid)))
|
||||
.left_join(
|
||||
users_organizations::table.on(ciphers::organization_uuid
|
||||
.eq(users_organizations::org_uuid.nullable())
|
||||
.and(users_organizations::user_uuid.eq(user_uuid))
|
||||
.and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
))
|
||||
.left_join(users_collections::table.on(
|
||||
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
||||
.and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))),
|
||||
)
|
||||
.left_join(
|
||||
users_collections::table.on(ciphers_collections::collection_uuid
|
||||
.eq(users_collections::collection_uuid)
|
||||
// Ensure that users_collections::user_uuid is NULL for unconfirmed users.
|
||||
.and(users_organizations::user_uuid.eq(users_collections::user_uuid))
|
||||
))
|
||||
.and(users_organizations::user_uuid.eq(users_collections::user_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
|
||||
@@ -888,23 +885,18 @@ impl Cipher {
|
||||
|
||||
if !visible_only {
|
||||
query = query.or_filter(
|
||||
users_organizations::atype.le(MembershipType::Admin as i32) // Org admin/owner
|
||||
users_organizations::atype.le(MembershipType::Admin as i32), // Org admin/owner
|
||||
);
|
||||
}
|
||||
|
||||
// Only filter for one specific cipher
|
||||
if !cipher_uuids.is_empty() {
|
||||
query = query.filter(
|
||||
ciphers::uuid.eq_any(cipher_uuids)
|
||||
);
|
||||
query = query.filter(ciphers::uuid.eq_any(cipher_uuids));
|
||||
}
|
||||
|
||||
query
|
||||
.select(ciphers::all_columns)
|
||||
.distinct()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
query.select(ciphers::all_columns).distinct().load::<Self>(conn).expect("Error loading ciphers")
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,193 +919,208 @@ impl Cipher {
|
||||
|
||||
// Find all ciphers directly owned by the specified user.
|
||||
pub async fn find_owned_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
ciphers::table
|
||||
.filter(
|
||||
ciphers::user_uuid.eq(user_uuid)
|
||||
.and(ciphers::organization_uuid.is_null())
|
||||
)
|
||||
.filter(ciphers::user_uuid.eq(user_uuid).and(ciphers::organization_uuid.is_null()))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_owned_by_user(user_uuid: &UserId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
ciphers::table.filter(ciphers::user_uuid.eq(user_uuid)).count().first::<i64>(conn).ok().unwrap_or(0)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
ciphers::table
|
||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
ciphers::table.filter(ciphers::organization_uuid.eq(org_uuid)).count().first::<i64>(conn).ok().unwrap_or(0)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_folder(folder_uuid: &FolderId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
folders_ciphers::table.inner_join(ciphers::table)
|
||||
conn.run(move |conn| {
|
||||
folders_ciphers::table
|
||||
.inner_join(ciphers::table)
|
||||
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
||||
.select(ciphers::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Find all ciphers that were deleted before the specified datetime.
|
||||
pub async fn find_deleted_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(ciphers::deleted_at.lt(dt))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
ciphers::table.filter(ciphers::deleted_at.lt(dt)).load::<Self>(conn).expect("Error loading ciphers")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_collections(&self, user_uuid: UserId, conn: &DbConn) -> Vec<CollectionId> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
ciphers_collections::table
|
||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(ciphers_collections::collection_uuid)
|
||||
))
|
||||
.left_join(users_organizations::table.on(
|
||||
users_organizations::org_uuid.eq(collections::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.left_join(users_collections::table.on(
|
||||
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_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(users_organizations::access_all.eq(true) // User has access all
|
||||
.or(users_collections::user_uuid.eq(user_uuid) // User has access to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.or(groups::access_all.eq(true)) // Access via groups
|
||||
.or(collections_groups::collections_uuid.is_not_null() // Access via groups
|
||||
.and(collections_groups::read_only.eq(false)))
|
||||
.inner_join(collections::table.on(collections::uuid.eq(ciphers_collections::collection_uuid)))
|
||||
.left_join(
|
||||
users_organizations::table.on(users_organizations::org_uuid
|
||||
.eq(collections::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.left_join(
|
||||
users_collections::table.on(users_collections::collection_uuid
|
||||
.eq(ciphers_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_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(
|
||||
users_organizations::access_all
|
||||
.eq(true) // User has access all
|
||||
.or(users_collections::user_uuid
|
||||
.eq(user_uuid) // User has access to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.or(groups::access_all.eq(true)) // Access via groups
|
||||
.or(collections_groups::collections_uuid
|
||||
.is_not_null() // Access via groups
|
||||
.and(collections_groups::read_only.eq(false))),
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<CollectionId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
ciphers_collections::table
|
||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(ciphers_collections::collection_uuid)
|
||||
))
|
||||
.inner_join(users_organizations::table.on(
|
||||
users_organizations::org_uuid.eq(collections::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.left_join(users_collections::table.on(
|
||||
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.filter(users_organizations::access_all.eq(true) // User has access all
|
||||
.or(users_collections::user_uuid.eq(user_uuid) // User has access to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.inner_join(collections::table.on(collections::uuid.eq(ciphers_collections::collection_uuid)))
|
||||
.inner_join(
|
||||
users_organizations::table.on(users_organizations::org_uuid
|
||||
.eq(collections::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.left_join(
|
||||
users_collections::table.on(users_collections::collection_uuid
|
||||
.eq(ciphers_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.filter(
|
||||
users_organizations::access_all
|
||||
.eq(true) // User has access all
|
||||
.or(users_collections::user_uuid
|
||||
.eq(user_uuid) // User has access to collection
|
||||
.and(users_collections::read_only.eq(false))),
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<CollectionId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_admin_collections(&self, user_uuid: UserId, conn: &DbConn) -> Vec<CollectionId> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
ciphers_collections::table
|
||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(ciphers_collections::collection_uuid)
|
||||
))
|
||||
.left_join(users_organizations::table.on(
|
||||
users_organizations::org_uuid.eq(collections::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.left_join(users_collections::table.on(
|
||||
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_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(users_organizations::access_all.eq(true) // User has access all
|
||||
.or(users_collections::user_uuid.eq(user_uuid) // User has access to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.or(groups::access_all.eq(true)) // Access via groups
|
||||
.or(collections_groups::collections_uuid.is_not_null() // Access via groups
|
||||
.and(collections_groups::read_only.eq(false)))
|
||||
.or(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
|
||||
.inner_join(collections::table.on(collections::uuid.eq(ciphers_collections::collection_uuid)))
|
||||
.left_join(
|
||||
users_organizations::table.on(users_organizations::org_uuid
|
||||
.eq(collections::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.left_join(
|
||||
users_collections::table.on(users_collections::collection_uuid
|
||||
.eq(ciphers_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_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(
|
||||
users_organizations::access_all
|
||||
.eq(true) // User has access all
|
||||
.or(users_collections::user_uuid
|
||||
.eq(user_uuid) // User has access to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.or(groups::access_all.eq(true)) // Access via groups
|
||||
.or(collections_groups::collections_uuid
|
||||
.is_not_null() // Access via groups
|
||||
.and(collections_groups::read_only.eq(false)))
|
||||
.or(users_organizations::atype.le(MembershipType::Admin as i32)), // User is admin or owner
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<CollectionId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
ciphers_collections::table
|
||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(ciphers_collections::collection_uuid)
|
||||
))
|
||||
.inner_join(users_organizations::table.on(
|
||||
users_organizations::org_uuid.eq(collections::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.left_join(users_collections::table.on(
|
||||
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.filter(users_organizations::access_all.eq(true) // User has access all
|
||||
.or(users_collections::user_uuid.eq(user_uuid) // User has access to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.or(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
|
||||
.inner_join(collections::table.on(collections::uuid.eq(ciphers_collections::collection_uuid)))
|
||||
.inner_join(
|
||||
users_organizations::table.on(users_organizations::org_uuid
|
||||
.eq(collections::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.left_join(
|
||||
users_collections::table.on(users_collections::collection_uuid
|
||||
.eq(ciphers_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.filter(
|
||||
users_organizations::access_all
|
||||
.eq(true) // User has access all
|
||||
.or(users_collections::user_uuid
|
||||
.eq(user_uuid) // User has access to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.or(users_organizations::atype.le(MembershipType::Admin as i32)), // User is admin or owner
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<CollectionId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1123,42 +1130,41 @@ impl Cipher {
|
||||
user_uuid: UserId,
|
||||
conn: &DbConn,
|
||||
) -> Vec<(CipherId, CollectionId)> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
ciphers_collections::table
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(ciphers_collections::collection_uuid)
|
||||
))
|
||||
.inner_join(users_organizations::table.on(
|
||||
users_organizations::org_uuid.eq(collections::org_uuid).and(
|
||||
users_organizations::user_uuid.eq(user_uuid.clone())
|
||||
.inner_join(collections::table.on(collections::uuid.eq(ciphers_collections::collection_uuid)))
|
||||
.inner_join(
|
||||
users_organizations::table.on(users_organizations::org_uuid
|
||||
.eq(collections::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
))
|
||||
.left_join(users_collections::table.on(
|
||||
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid).and(
|
||||
users_collections::user_uuid.eq(user_uuid.clone())
|
||||
.left_join(
|
||||
users_collections::table.on(users_collections::collection_uuid
|
||||
.eq(ciphers_collections::collection_uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
))
|
||||
.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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))
|
||||
))
|
||||
.left_join(collections_groups::table.on(
|
||||
collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid).and(
|
||||
collections_groups::groups_uuid.eq(groups::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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||
)
|
||||
))
|
||||
.or_filter(users_collections::user_uuid.eq(user_uuid)) // User has access to collection
|
||||
.or_filter(users_organizations::access_all.eq(true)) // User has access all
|
||||
.or_filter(users_organizations::atype.le(MembershipType::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)
|
||||
.distinct()
|
||||
.load::<(CipherId, CollectionId)>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
.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_uuid)) // User has access to collection
|
||||
.or_filter(users_organizations::access_all.eq(true)) // User has access all
|
||||
.or_filter(users_organizations::atype.le(MembershipType::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)
|
||||
.distinct()
|
||||
.load::<(CipherId, CollectionId)>(conn)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+402
-309
@@ -1,16 +1,25 @@
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::EmptyResult,
|
||||
db::{
|
||||
DbConn,
|
||||
schema::{
|
||||
ciphers_collections, collections, collections_groups, groups, groups_users, users_collections,
|
||||
users_organizations,
|
||||
},
|
||||
},
|
||||
error::MapResult,
|
||||
};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
use super::{
|
||||
CipherId, CollectionGroup, GroupUser, Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId,
|
||||
User, UserId,
|
||||
};
|
||||
use crate::db::schema::{
|
||||
ciphers_collections, collections, collections_groups, groups, groups_users, users_collections, users_organizations,
|
||||
};
|
||||
use crate::CONFIG;
|
||||
use diesel::prelude::*;
|
||||
use macros::UuidFromParam;
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = collections)]
|
||||
@@ -74,7 +83,7 @@ impl Collection {
|
||||
if external_id.is_empty() {
|
||||
self.external_id = None;
|
||||
} else {
|
||||
self.external_id = Some(external_id)
|
||||
self.external_id = Some(external_id);
|
||||
}
|
||||
}
|
||||
None => self.external_id = None,
|
||||
@@ -147,11 +156,6 @@ impl Collection {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Collection {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
@@ -193,11 +197,12 @@ impl Collection {
|
||||
CollectionUser::delete_all_by_collection(&self.uuid, conn).await?;
|
||||
CollectionGroup::delete_all_by_collection(&self.uuid, &self.org_uuid, conn).await?;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(collections::table.filter(collections::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting collection")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -208,90 +213,90 @@ impl Collection {
|
||||
}
|
||||
|
||||
pub async fn update_users_revision(&self, conn: &DbConn) {
|
||||
for member in Membership::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).await.iter() {
|
||||
for member in &Membership::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).await {
|
||||
User::update_uuid_revision(&member.user_uuid, conn).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &CollectionId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
.filter(collections::uuid.eq(uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| collections::table.filter(collections::uuid.eq(uuid)).first::<Self>(conn).ok()).await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_uuid(user_uuid: UserId, conn: &DbConn) -> Vec<Self> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! { conn: {
|
||||
conn.run(move |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_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.clone())
|
||||
.left_join(
|
||||
users_organizations::table.on(collections::org_uuid
|
||||
.eq(users_organizations::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
))
|
||||
.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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))
|
||||
))
|
||||
.left_join(collections_groups::table.on(
|
||||
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
||||
collections_groups::collections_uuid.eq(collections::uuid)
|
||||
.left_join(
|
||||
groups_users::table.on(groups_users::users_organizations_uuid.eq(users_organizations::uuid)),
|
||||
)
|
||||
))
|
||||
.filter(
|
||||
users_organizations::status.eq(MembershipStatus::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()
|
||||
)
|
||||
.left_join(
|
||||
groups::table.on(groups::uuid
|
||||
.eq(groups_users::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||
)
|
||||
)
|
||||
.select(collections::all_columns)
|
||||
.distinct()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collections")
|
||||
}}
|
||||
.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(MembershipStatus::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)
|
||||
.distinct()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collections")
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
db_run! { conn: {
|
||||
conn.run(move |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_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.clone())
|
||||
.left_join(
|
||||
users_organizations::table.on(collections::org_uuid
|
||||
.eq(users_organizations::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
))
|
||||
.filter(
|
||||
users_organizations::status.eq(MembershipStatus::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
|
||||
)
|
||||
)
|
||||
.select(collections::all_columns)
|
||||
.distinct()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collections")
|
||||
}}
|
||||
.filter(users_organizations::status.eq(MembershipStatus::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
|
||||
))
|
||||
.select(collections::all_columns)
|
||||
.distinct()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collections")
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,256 +313,311 @@ impl Collection {
|
||||
}
|
||||
|
||||
pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
collections::table
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collections")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
collections::table.filter(collections::org_uuid.eq(org_uuid)).count().first::<i64>(conn).ok().unwrap_or(0)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_org(uuid: &CollectionId, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
collections::table
|
||||
.filter(collections::uuid.eq(uuid))
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
.select(collections::all_columns)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &CollectionId, user_uuid: UserId, conn: &DbConn) -> Option<Self> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! { conn: {
|
||||
conn.run(move |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_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(
|
||||
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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))
|
||||
))
|
||||
.left_join(collections_groups::table.on(
|
||||
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
||||
collections_groups::collections_uuid.eq(collections::uuid)
|
||||
.left_join(
|
||||
groups_users::table.on(groups_users::users_organizations_uuid.eq(users_organizations::uuid)),
|
||||
)
|
||||
))
|
||||
.filter(collections::uuid.eq(uuid))
|
||||
.filter(
|
||||
users_collections::collection_uuid.eq(uuid).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()
|
||||
)
|
||||
.left_join(
|
||||
groups::table.on(groups::uuid
|
||||
.eq(groups_users::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||
)
|
||||
).select(collections::all_columns)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
.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(uuid))
|
||||
.filter(
|
||||
users_collections::collection_uuid
|
||||
.eq(uuid)
|
||||
.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()),
|
||||
),
|
||||
)
|
||||
.select(collections::all_columns)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
db_run! { conn: {
|
||||
conn.run(move |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_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(
|
||||
users_organizations::table.on(collections::org_uuid
|
||||
.eq(users_organizations::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid))),
|
||||
)
|
||||
))
|
||||
.filter(collections::uuid.eq(uuid))
|
||||
.filter(
|
||||
users_collections::collection_uuid.eq(uuid).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
|
||||
.filter(collections::uuid.eq(uuid))
|
||||
.filter(users_collections::collection_uuid.eq(uuid).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
|
||||
),
|
||||
))
|
||||
).select(collections::all_columns)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
.select(collections::all_columns)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn is_writable_by_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
let user_uuid = user_uuid.to_string();
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
collections::table
|
||||
.filter(collections::uuid.eq(&self.uuid))
|
||||
.inner_join(users_organizations::table.on(
|
||||
collections::org_uuid.eq(users_organizations::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.left_join(users_collections::table.on(
|
||||
users_collections::collection_uuid.eq(collections::uuid)
|
||||
.and(users_collections::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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_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::atype.le(MembershipType::Admin as i32) // Org admin or owner
|
||||
.or(users_organizations::access_all.eq(true)) // access_all via membership
|
||||
.or(users_collections::collection_uuid.eq(&self.uuid) // write access given to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.or(groups::access_all.eq(true)) // access_all via group
|
||||
.or(collections_groups::collections_uuid.is_not_null() // write access given via group
|
||||
.and(collections_groups::read_only.eq(false)))
|
||||
.inner_join(
|
||||
users_organizations::table.on(collections::org_uuid
|
||||
.eq(users_organizations::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.left_join(
|
||||
users_collections::table.on(users_collections::collection_uuid
|
||||
.eq(collections::uuid)
|
||||
.and(users_collections::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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_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::atype
|
||||
.le(MembershipType::Admin as i32) // Org admin or owner
|
||||
.or(users_organizations::access_all.eq(true)) // access_all via membership
|
||||
.or(users_collections::collection_uuid
|
||||
.eq(&self.uuid) // write access given to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.or(groups::access_all.eq(true)) // access_all via group
|
||||
.or(collections_groups::collections_uuid
|
||||
.is_not_null() // write access given via group
|
||||
.and(collections_groups::read_only.eq(false))),
|
||||
)
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
.unwrap_or(0)
|
||||
!= 0
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
collections::table
|
||||
.filter(collections::uuid.eq(&self.uuid))
|
||||
.inner_join(users_organizations::table.on(
|
||||
collections::org_uuid.eq(users_organizations::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))
|
||||
))
|
||||
.left_join(users_collections::table.on(
|
||||
users_collections::collection_uuid.eq(collections::uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid))
|
||||
))
|
||||
.filter(users_organizations::atype.le(MembershipType::Admin as i32) // Org admin or owner
|
||||
.or(users_organizations::access_all.eq(true)) // access_all via membership
|
||||
.or(users_collections::collection_uuid.eq(&self.uuid) // write access given to collection
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
.inner_join(
|
||||
users_organizations::table.on(collections::org_uuid
|
||||
.eq(users_organizations::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid.clone()))),
|
||||
)
|
||||
.left_join(
|
||||
users_collections::table.on(users_collections::collection_uuid
|
||||
.eq(collections::uuid)
|
||||
.and(users_collections::user_uuid.eq(user_uuid))),
|
||||
)
|
||||
.filter(
|
||||
users_organizations::atype
|
||||
.le(MembershipType::Admin as i32) // Org admin or owner
|
||||
.or(users_organizations::access_all.eq(true)) // access_all via membership
|
||||
.or(users_collections::collection_uuid
|
||||
.eq(&self.uuid) // write access given to collection
|
||||
.and(users_collections::read_only.eq(false))),
|
||||
)
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
.unwrap_or(0)
|
||||
!= 0
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn hide_passwords_for_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
let user_uuid = user_uuid.to_string();
|
||||
db_run! { conn: {
|
||||
conn.run(move |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_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(
|
||||
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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))
|
||||
))
|
||||
.left_join(collections_groups::table.on(
|
||||
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
||||
collections_groups::collections_uuid.eq(collections::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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||
)
|
||||
))
|
||||
.filter(collections::uuid.eq(&self.uuid))
|
||||
.filter(
|
||||
users_collections::collection_uuid.eq(&self.uuid).and(users_collections::hide_passwords.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::hide_passwords.eq(true))
|
||||
)
|
||||
.left_join(
|
||||
collections_groups::table.on(collections_groups::groups_uuid
|
||||
.eq(groups_users::groups_uuid)
|
||||
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
||||
)
|
||||
)
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
.filter(collections::uuid.eq(&self.uuid))
|
||||
.filter(
|
||||
users_collections::collection_uuid
|
||||
.eq(&self.uuid)
|
||||
.and(users_collections::hide_passwords.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::hide_passwords.eq(true)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0)
|
||||
!= 0
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn is_coll_manageable_by_user(uuid: &CollectionId, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
let uuid = uuid.to_string();
|
||||
let user_uuid = user_uuid.to_string();
|
||||
db_run! { conn: {
|
||||
conn.run(move |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_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(
|
||||
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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))
|
||||
))
|
||||
.left_join(collections_groups::table.on(
|
||||
collections_groups::groups_uuid.eq(groups_users::groups_uuid).and(
|
||||
collections_groups::collections_uuid.eq(collections::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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||
)
|
||||
))
|
||||
.filter(collections::uuid.eq(&uuid))
|
||||
.filter(
|
||||
users_collections::collection_uuid.eq(&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))
|
||||
)
|
||||
.left_join(
|
||||
collections_groups::table.on(collections_groups::groups_uuid
|
||||
.eq(groups_users::groups_uuid)
|
||||
.and(collections_groups::collections_uuid.eq(collections::uuid))),
|
||||
)
|
||||
)
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
.filter(collections::uuid.eq(&uuid))
|
||||
.filter(
|
||||
users_collections::collection_uuid
|
||||
.eq(&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
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn is_manageable_by_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
@@ -572,7 +632,7 @@ impl CollectionUser {
|
||||
user_uuid: &UserId,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_collections::table
|
||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
||||
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
|
||||
@@ -580,24 +640,35 @@ impl CollectionUser {
|
||||
.select(users_collections::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_organization_swap_user_uuid_with_member_uuid(
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &DbConn,
|
||||
) -> Vec<CollectionMembership> {
|
||||
let col_users = db_run! { conn: {
|
||||
users_collections::table
|
||||
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
.inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid)))
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords, users_collections::manage))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
}};
|
||||
col_users.into_iter().map(|c| c.into()).collect()
|
||||
let col_users = conn
|
||||
.run(move |conn| {
|
||||
users_collections::table
|
||||
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
.inner_join(
|
||||
users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid)),
|
||||
)
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.select((
|
||||
users_organizations::uuid,
|
||||
users_collections::collection_uuid,
|
||||
users_collections::read_only,
|
||||
users_collections::hide_passwords,
|
||||
users_collections::manage,
|
||||
))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
})
|
||||
.await;
|
||||
col_users.into_iter().map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub async fn save(
|
||||
@@ -666,7 +737,7 @@ impl CollectionUser {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(&self.user_uuid, conn).await;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(
|
||||
users_collections::table
|
||||
.filter(users_collections::user_uuid.eq(&self.user_uuid))
|
||||
@@ -674,17 +745,19 @@ impl CollectionUser {
|
||||
)
|
||||
.execute(conn)
|
||||
.map_res("Error removing user from collection")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_collections::table
|
||||
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
||||
.select(users_collections::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_org_and_coll_swap_user_uuid_with_member_uuid(
|
||||
@@ -692,16 +765,26 @@ impl CollectionUser {
|
||||
collection_uuid: &CollectionId,
|
||||
conn: &DbConn,
|
||||
) -> Vec<CollectionMembership> {
|
||||
let col_users = db_run! { conn: {
|
||||
users_collections::table
|
||||
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
||||
.filter(users_organizations::org_uuid.eq(org_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, users_collections::manage))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
}};
|
||||
col_users.into_iter().map(|c| c.into()).collect()
|
||||
let col_users = conn
|
||||
.run(move |conn| {
|
||||
users_collections::table
|
||||
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
||||
.filter(users_organizations::org_uuid.eq(org_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,
|
||||
users_collections::manage,
|
||||
))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
})
|
||||
.await;
|
||||
col_users.into_iter().map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub async fn find_by_collection_and_user(
|
||||
@@ -709,36 +792,39 @@ impl CollectionUser {
|
||||
user_uuid: &UserId,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_collections::table
|
||||
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
||||
.select(users_collections::all_columns)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_collections::table
|
||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
||||
.select(users_collections::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &DbConn) -> EmptyResult {
|
||||
for collection in CollectionUser::find_by_collection(collection_uuid, conn).await.iter() {
|
||||
for collection in &CollectionUser::find_by_collection(collection_uuid, conn).await {
|
||||
User::update_uuid_revision(&collection.user_uuid, conn).await;
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting users from collection")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user_and_org(
|
||||
@@ -748,17 +834,21 @@ impl CollectionUser {
|
||||
) -> EmptyResult {
|
||||
let collectionusers = Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn).await;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
for user in collectionusers {
|
||||
let _: () = diesel::delete(users_collections::table.filter(
|
||||
users_collections::user_uuid.eq(user_uuid)
|
||||
.and(users_collections::collection_uuid.eq(user.collection_uuid))
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error removing user from collections")?;
|
||||
let _: () = diesel::delete(
|
||||
users_collections::table.filter(
|
||||
users_collections::user_uuid
|
||||
.eq(user_uuid)
|
||||
.and(users_collections::collection_uuid.eq(user.collection_uuid)),
|
||||
),
|
||||
)
|
||||
.execute(conn)
|
||||
.map_res("Error removing user from collections")?;
|
||||
}
|
||||
Ok(())
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn has_access_to_collection_by_user(col_id: &CollectionId, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
@@ -801,7 +891,7 @@ impl CollectionCipher {
|
||||
pub async fn delete(cipher_uuid: &CipherId, collection_uuid: &CollectionId, conn: &DbConn) -> EmptyResult {
|
||||
Self::update_users_revision(collection_uuid, conn).await;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(
|
||||
ciphers_collections::table
|
||||
.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))
|
||||
@@ -809,23 +899,26 @@ impl CollectionCipher {
|
||||
)
|
||||
.execute(conn)
|
||||
.map_res("Error deleting cipher from collection")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing cipher from collections")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing ciphers from collection")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_users_revision(collection_uuid: &CollectionId, conn: &DbConn) {
|
||||
|
||||
+39
-44
@@ -1,18 +1,20 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
|
||||
use data_encoding::BASE64URL;
|
||||
use derive_more::{Display, From};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{AuthRequest, UserId};
|
||||
use crate::db::schema::devices;
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
crypto,
|
||||
db::{DbConn, schema::devices},
|
||||
error::MapResult,
|
||||
util::{format_date, get_uuid},
|
||||
};
|
||||
use diesel::prelude::*;
|
||||
use macros::{IdFromParam, UuidFromParam};
|
||||
|
||||
use super::{AuthRequest, UserId};
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = devices)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
@@ -135,10 +137,6 @@ impl DeviceWithAuthRequest {
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Device {
|
||||
@@ -171,21 +169,23 @@ impl Device {
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(devices::table.filter(devices::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing devices for user")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &DeviceId, user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
devices::table
|
||||
.filter(devices::uuid.eq(uuid))
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_with_auth_request_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<DeviceWithAuthRequest> {
|
||||
@@ -199,71 +199,65 @@ impl Device {
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
devices::table
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading devices")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
devices::table.filter(devices::user_uuid.eq(user_uuid)).load::<Self>(conn).expect("Error loading devices")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &DeviceId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
devices::table
|
||||
.filter(devices::uuid.eq(uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| devices::table.filter(devices::uuid.eq(uuid)).first::<Self>(conn).ok()).await
|
||||
}
|
||||
|
||||
pub async fn clear_push_token_by_uuid(uuid: &DeviceId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::update(devices::table)
|
||||
.filter(devices::uuid.eq(uuid))
|
||||
.set(devices::push_token.eq::<Option<String>>(None))
|
||||
.execute(conn)
|
||||
.map_res("Error removing push token")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
pub async fn find_by_refresh_token(refresh_token: &str, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
devices::table
|
||||
.filter(devices::refresh_token.eq(refresh_token))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| devices::table.filter(devices::refresh_token.eq(refresh_token)).first::<Self>(conn).ok())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_latest_active_by_user(user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
devices::table
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
.order(devices::updated_at.desc())
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_push_devices_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
devices::table
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
.filter(devices::push_token.is_not_null())
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading push devices")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn check_user_has_push_device(user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
devices::table
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
.filter(devices::push_token.is_not_null())
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
.filter(devices::push_token.is_not_null())
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0)
|
||||
!= 0
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn rotate_refresh_tokens_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -337,6 +331,7 @@ pub enum DeviceType {
|
||||
}
|
||||
|
||||
impl DeviceType {
|
||||
#[expect(clippy::match_same_arms, reason = "Specifically define 14 and have a fallback for new types")]
|
||||
pub fn from_i32(value: i32) -> DeviceType {
|
||||
match value {
|
||||
0 => DeviceType::Android,
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{User, UserId};
|
||||
use crate::db::schema::emergency_access;
|
||||
use crate::{api::EmptyResult, db::DbConn, error::MapResult};
|
||||
use diesel::prelude::*;
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
db::{DbConn, schema::emergency_access},
|
||||
error::MapResult,
|
||||
};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
use super::{User, UserId};
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = emergency_access)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
@@ -87,13 +91,12 @@ impl EmergencyAccess {
|
||||
User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")
|
||||
} else {
|
||||
let email = self.email.as_deref()?;
|
||||
match User::find_by_mail(email, conn).await {
|
||||
Some(user) => user,
|
||||
None => {
|
||||
// remove outstanding invitations which should not exist
|
||||
Self::delete_all_by_grantee_email(email, conn).await.ok();
|
||||
return None;
|
||||
}
|
||||
if let Some(user) = User::find_by_mail(email, conn).await {
|
||||
user
|
||||
} else {
|
||||
// remove outstanding invitations which should not exist
|
||||
Self::delete_all_by_grantee_email(email, conn).await.ok();
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -183,28 +186,36 @@ impl EmergencyAccess {
|
||||
self.status = status;
|
||||
date.clone_into(&mut self.updated_at);
|
||||
|
||||
db_run! { conn: {
|
||||
crate::util::retry(|| {
|
||||
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid)))
|
||||
.set((emergency_access::status.eq(status), emergency_access::updated_at.eq(date)))
|
||||
.execute(conn)
|
||||
}, 10)
|
||||
conn.run(move |conn| {
|
||||
crate::util::retry(
|
||||
|| {
|
||||
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid)))
|
||||
.set((emergency_access::status.eq(status), emergency_access::updated_at.eq(date)))
|
||||
.execute(conn)
|
||||
},
|
||||
10,
|
||||
)
|
||||
.map_res("Error updating emergency access status")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_last_notification_date_and_save(&mut self, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
|
||||
self.last_notification_at = Some(date.to_owned());
|
||||
date.clone_into(&mut self.updated_at);
|
||||
|
||||
db_run! { conn: {
|
||||
crate::util::retry(|| {
|
||||
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid)))
|
||||
.set((emergency_access::last_notification_at.eq(date), emergency_access::updated_at.eq(date)))
|
||||
.execute(conn)
|
||||
}, 10)
|
||||
conn.run(move |conn| {
|
||||
crate::util::retry(
|
||||
|| {
|
||||
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid)))
|
||||
.set((emergency_access::last_notification_at.eq(date), emergency_access::updated_at.eq(date)))
|
||||
.execute(conn)
|
||||
},
|
||||
10,
|
||||
)
|
||||
.map_res("Error updating emergency access status")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -227,11 +238,12 @@ impl EmergencyAccess {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(&self.grantor_uuid, conn).await;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(emergency_access::table.filter(emergency_access::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing user from emergency access")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_grantor_uuid_and_grantee_uuid_or_email(
|
||||
@@ -240,23 +252,25 @@ impl EmergencyAccess {
|
||||
email: &str,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::grantor_uuid.eq(grantor_uuid))
|
||||
.filter(emergency_access::grantee_uuid.eq(grantee_uuid).or(emergency_access::email.eq(email)))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_all_recoveries_initiated(conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32))
|
||||
.filter(emergency_access::recovery_initiated_at.is_not_null())
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading emergency_access")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_grantor_uuid(
|
||||
@@ -264,13 +278,14 @@ impl EmergencyAccess {
|
||||
grantor_uuid: &UserId,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::uuid.eq(uuid))
|
||||
.filter(emergency_access::grantor_uuid.eq(grantor_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_grantee_uuid(
|
||||
@@ -278,13 +293,14 @@ impl EmergencyAccess {
|
||||
grantee_uuid: &UserId,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::uuid.eq(uuid))
|
||||
.filter(emergency_access::grantee_uuid.eq(grantee_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_grantee_email(
|
||||
@@ -292,61 +308,67 @@ impl EmergencyAccess {
|
||||
grantee_email: &str,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::uuid.eq(uuid))
|
||||
.filter(emergency_access::email.eq(grantee_email))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_all_by_grantee_uuid(grantee_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::grantee_uuid.eq(grantee_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading emergency_access")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_invited_by_grantee_email(grantee_email: &str, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::email.eq(grantee_email))
|
||||
.filter(emergency_access::status.eq(EmergencyAccessStatus::Invited as i32))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_all_invited_by_grantee_email(grantee_email: &str, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::email.eq(grantee_email))
|
||||
.filter(emergency_access::status.eq(EmergencyAccessStatus::Invited as i32))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading emergency_access")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_all_by_grantor_uuid(grantor_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::grantor_uuid.eq(grantor_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading emergency_access")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_all_confirmed_by_grantor_uuid(grantor_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::grantor_uuid.eq(grantor_uuid))
|
||||
.filter(emergency_access::status.ge(EmergencyAccessStatus::Confirmed as i32))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading emergency_access")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn accept_invite(&mut self, grantee_uuid: &UserId, grantee_email: &str, conn: &DbConn) -> EmptyResult {
|
||||
|
||||
+38
-28
@@ -1,11 +1,18 @@
|
||||
use chrono::{NaiveDateTime, TimeDelta, Utc};
|
||||
//use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::EmptyResult,
|
||||
db::{
|
||||
DbConn,
|
||||
schema::{event, users_organizations},
|
||||
},
|
||||
error::MapResult,
|
||||
};
|
||||
|
||||
use super::{CipherId, CollectionId, GroupId, MembershipId, OrgPolicyId, OrganizationId, UserId};
|
||||
use crate::db::schema::{event, users_organizations};
|
||||
use crate::{api::EmptyResult, db::DbConn, error::MapResult, CONFIG};
|
||||
use diesel::prelude::*;
|
||||
|
||||
// https://bitwarden.com/help/event-logs/
|
||||
|
||||
@@ -249,11 +256,10 @@ impl Event {
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(event::table.filter(event::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting event")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(event::table.filter(event::uuid.eq(self.uuid))).execute(conn).map_res("Error deleting event")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// ##############
|
||||
@@ -264,7 +270,7 @@ impl Event {
|
||||
end: &NaiveDateTime,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
event::table
|
||||
.filter(event::org_uuid.eq(org_uuid))
|
||||
.filter(event::event_date.between(start, end))
|
||||
@@ -272,18 +278,15 @@ impl Event {
|
||||
.limit(Self::PAGE_SIZE)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error filtering events")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
event::table
|
||||
.filter(event::org_uuid.eq(org_uuid))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
event::table.filter(event::org_uuid.eq(org_uuid)).count().first::<i64>(conn).ok().unwrap_or(0)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_org_and_member(
|
||||
@@ -293,18 +296,23 @@ impl Event {
|
||||
end: &NaiveDateTime,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
event::table
|
||||
.inner_join(users_organizations::table.on(users_organizations::uuid.eq(member_uuid)))
|
||||
.filter(event::org_uuid.eq(org_uuid))
|
||||
.filter(event::event_date.between(start, end))
|
||||
.filter(event::user_uuid.eq(users_organizations::user_uuid.nullable()).or(event::act_user_uuid.eq(users_organizations::user_uuid.nullable())))
|
||||
.filter(
|
||||
event::user_uuid
|
||||
.eq(users_organizations::user_uuid.nullable())
|
||||
.or(event::act_user_uuid.eq(users_organizations::user_uuid.nullable())),
|
||||
)
|
||||
.select(event::all_columns)
|
||||
.order_by(event::event_date.desc())
|
||||
.limit(Self::PAGE_SIZE)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error filtering events")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_cipher_uuid(
|
||||
@@ -313,7 +321,7 @@ impl Event {
|
||||
end: &NaiveDateTime,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
event::table
|
||||
.filter(event::cipher_uuid.eq(cipher_uuid))
|
||||
.filter(event::event_date.between(start, end))
|
||||
@@ -321,17 +329,19 @@ impl Event {
|
||||
.limit(Self::PAGE_SIZE)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error filtering events")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn clean_events(conn: &DbConn) -> EmptyResult {
|
||||
if let Some(days_to_retain) = CONFIG.events_days_retain() {
|
||||
let dt = Utc::now().naive_utc() - TimeDelta::try_days(days_to_retain).unwrap();
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(event::table.filter(event::event_date.lt(dt)))
|
||||
.execute(conn)
|
||||
.map_res("Error cleaning old events")
|
||||
}}
|
||||
.execute(conn)
|
||||
.map_res("Error cleaning old events")
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+32
-30
@@ -1,7 +1,13 @@
|
||||
use super::{CipherId, User, UserId};
|
||||
use crate::db::schema::favorites;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
db::{DbConn, schema::favorites},
|
||||
error::MapResult,
|
||||
};
|
||||
|
||||
use super::{CipherId, User, UserId};
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = favorites)]
|
||||
#[diesel(primary_key(user_uuid, cipher_uuid))]
|
||||
@@ -10,24 +16,18 @@ pub struct Favorite {
|
||||
pub cipher_uuid: CipherId,
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
impl Favorite {
|
||||
// Returns whether the specified cipher is a favorite of the specified user.
|
||||
pub async fn is_favorite(cipher_uuid: &CipherId, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
let query = favorites::table
|
||||
.filter(favorites::cipher_uuid.eq(cipher_uuid))
|
||||
.filter(favorites::user_uuid.eq(user_uuid))
|
||||
.count();
|
||||
|
||||
query.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
query.first::<i64>(conn).ok().unwrap_or(0) != 0
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// Sets whether the specified cipher is a favorite of the specified user.
|
||||
@@ -41,27 +41,26 @@ impl Favorite {
|
||||
match (old, new) {
|
||||
(false, true) => {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
db_run! { conn: {
|
||||
diesel::insert_into(favorites::table)
|
||||
.values((
|
||||
favorites::user_uuid.eq(user_uuid),
|
||||
favorites::cipher_uuid.eq(cipher_uuid),
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error adding favorite")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
diesel::insert_into(favorites::table)
|
||||
.values((favorites::user_uuid.eq(user_uuid), favorites::cipher_uuid.eq(cipher_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error adding favorite")
|
||||
})
|
||||
.await
|
||||
}
|
||||
(true, false) => {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(
|
||||
favorites::table
|
||||
.filter(favorites::user_uuid.eq(user_uuid))
|
||||
.filter(favorites::cipher_uuid.eq(cipher_uuid))
|
||||
.filter(favorites::cipher_uuid.eq(cipher_uuid)),
|
||||
)
|
||||
.execute(conn)
|
||||
.map_res("Error removing favorite")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
// Otherwise, the favorite status is already what it should be.
|
||||
_ => Ok(()),
|
||||
@@ -70,31 +69,34 @@ impl Favorite {
|
||||
|
||||
// Delete all favorite entries associated with the specified cipher.
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing favorites by cipher")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// Delete all favorite entries associated with the specified user.
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(favorites::table.filter(favorites::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing favorites by user")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return a vec with (cipher_uuid) this will only contain favorite flagged ciphers
|
||||
/// This is used during a full sync so we only need one query for all favorite cipher matches.
|
||||
pub async fn get_all_cipher_uuid_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<CipherId> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
favorites::table
|
||||
.filter(favorites::user_uuid.eq(user_uuid))
|
||||
.select(favorites::cipher_uuid)
|
||||
.load::<CipherId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
+40
-31
@@ -1,12 +1,20 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{CipherId, User, UserId};
|
||||
use crate::db::schema::{folders, folders_ciphers};
|
||||
use diesel::prelude::*;
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
db::{
|
||||
DbConn,
|
||||
schema::{folders, folders_ciphers},
|
||||
},
|
||||
error::MapResult,
|
||||
};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
use super::{CipherId, User, UserId};
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = folders)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
@@ -56,17 +64,12 @@ impl Folder {
|
||||
impl FolderCipher {
|
||||
pub fn new(folder_uuid: FolderId, cipher_uuid: CipherId) -> Self {
|
||||
Self {
|
||||
folder_uuid,
|
||||
cipher_uuid,
|
||||
folder_uuid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Folder {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
@@ -107,11 +110,12 @@ impl Folder {
|
||||
User::update_uuid_revision(&self.user_uuid, conn).await;
|
||||
FolderCipher::delete_all_by_folder(&self.uuid, conn).await?;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(folders::table.filter(folders::uuid.eq(&self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting folder")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -122,22 +126,21 @@ impl Folder {
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &FolderId, user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
folders::table
|
||||
.filter(folders::uuid.eq(uuid))
|
||||
.filter(folders::user_uuid.eq(user_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
folders::table
|
||||
.filter(folders::user_uuid.eq(user_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading folders")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
folders::table.filter(folders::user_uuid.eq(user_uuid)).load::<Self>(conn).expect("Error loading folders")
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +168,7 @@ impl FolderCipher {
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(
|
||||
folders_ciphers::table
|
||||
.filter(folders_ciphers::cipher_uuid.eq(self.cipher_uuid))
|
||||
@@ -173,23 +176,26 @@ impl FolderCipher {
|
||||
)
|
||||
.execute(conn)
|
||||
.map_res("Error removing cipher from folder")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing cipher from folders")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_folder(folder_uuid: &FolderId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing ciphers from folder")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_folder_and_cipher(
|
||||
@@ -197,35 +203,38 @@ impl FolderCipher {
|
||||
cipher_uuid: &CipherId,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
folders_ciphers::table
|
||||
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
||||
.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_folder(folder_uuid: &FolderId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
folders_ciphers::table
|
||||
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading folders")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return a vec with (cipher_uuid, folder_uuid)
|
||||
/// This is used during a full sync so we only need one query for all folder matches.
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<(CipherId, FolderId)> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
folders_ciphers::table
|
||||
.inner_join(folders::table)
|
||||
.filter(folders::user_uuid.eq(user_uuid))
|
||||
.select(folders_ciphers::all_columns)
|
||||
.load::<(CipherId, FolderId)>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+143
-122
@@ -1,14 +1,20 @@
|
||||
use super::{CollectionId, Membership, MembershipId, OrganizationId, User, UserId};
|
||||
use crate::api::EmptyResult;
|
||||
use crate::db::schema::{collections, collections_groups, groups, groups_users, users_organizations};
|
||||
use crate::db::DbConn;
|
||||
use crate::error::MapResult;
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use macros::UuidFromParam;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
db::{
|
||||
DbConn,
|
||||
schema::{collections, collections_groups, groups, groups_users, users_organizations},
|
||||
},
|
||||
error::MapResult,
|
||||
};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
use super::{CollectionId, Membership, MembershipId, OrganizationId, User, UserId};
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = groups)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
@@ -197,33 +203,31 @@ impl Group {
|
||||
}
|
||||
|
||||
pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
groups::table
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading groups")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
groups::table
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
groups::table.filter(groups::organizations_uuid.eq(org_uuid)).count().first::<i64>(conn).ok().unwrap_or(0)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_org(uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
groups::table
|
||||
.filter(groups::uuid.eq(uuid))
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_external_id_and_org(
|
||||
@@ -231,77 +235,85 @@ impl Group {
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
groups::table
|
||||
.filter(groups::external_id.eq(external_id))
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
//Returns all organizations the user has full access to
|
||||
pub async fn get_orgs_by_user_with_full_access(user_uuid: &UserId, conn: &DbConn) -> Vec<OrganizationId> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))
|
||||
))
|
||||
.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)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||
)
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(groups::access_all.eq(true))
|
||||
.select(groups::organizations_uuid)
|
||||
.distinct()
|
||||
.load::<OrganizationId>(conn)
|
||||
.expect("Error loading organization group full access information for user")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn is_in_full_access_group(user_uuid: &UserId, org_uuid: &OrganizationId, conn: &DbConn) -> bool {
|
||||
db_run! { conn: {
|
||||
conn.run(move |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)
|
||||
))
|
||||
.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()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(&self, org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
CollectionGroup::delete_all_by_group(&self.uuid, org_uuid, conn).await?;
|
||||
GroupUser::delete_all_by_group(&self.uuid, org_uuid, conn).await?;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(groups::table.filter(groups::uuid.eq(&self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting group")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_revision(uuid: &GroupId, conn: &DbConn) {
|
||||
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
|
||||
if let Err(e) = Self::update_revision_impl(uuid, &Utc::now().naive_utc(), conn).await {
|
||||
warn!("Failed to update revision for {uuid}: {e:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn _update_revision(uuid: &GroupId, 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)
|
||||
async fn update_revision_impl(uuid: &GroupId, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
|
||||
conn.run(move |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")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,60 +378,63 @@ impl CollectionGroup {
|
||||
}
|
||||
|
||||
pub async fn find_by_group(group_uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
collections_groups::table
|
||||
.inner_join(groups::table.on(
|
||||
groups::uuid.eq(collections_groups::groups_uuid)
|
||||
))
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(collections_groups::collections_uuid)
|
||||
.and(collections::org_uuid.eq(groups::organizations_uuid))
|
||||
))
|
||||
.inner_join(groups::table.on(groups::uuid.eq(collections_groups::groups_uuid)))
|
||||
.inner_join(
|
||||
collections::table.on(collections::uuid
|
||||
.eq(collections_groups::collections_uuid)
|
||||
.and(collections::org_uuid.eq(groups::organizations_uuid))),
|
||||
)
|
||||
.filter(collections_groups::groups_uuid.eq(group_uuid))
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
.select(collections_groups::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collection groups")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |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)
|
||||
))
|
||||
.inner_join(groups::table.on(groups::uuid.eq(collections_groups::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))
|
||||
))
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(collections_groups::collections_uuid)
|
||||
.and(collections::org_uuid.eq(groups::organizations_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)),
|
||||
)
|
||||
.inner_join(
|
||||
groups::table.on(groups::uuid
|
||||
.eq(collections_groups::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||
)
|
||||
.inner_join(
|
||||
collections::table.on(collections::uuid
|
||||
.eq(collections_groups::collections_uuid)
|
||||
.and(collections::org_uuid.eq(groups::organizations_uuid))),
|
||||
)
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.select(collections_groups::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user collection groups")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
collections_groups::table
|
||||
.filter(collections_groups::collections_uuid.eq(collection_uuid))
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(collections_groups::collections_uuid)
|
||||
))
|
||||
.inner_join(groups::table.on(groups::uuid.eq(collections_groups::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(collections::org_uuid))
|
||||
))
|
||||
.inner_join(collections::table.on(collections::uuid.eq(collections_groups::collections_uuid)))
|
||||
.inner_join(
|
||||
groups::table.on(groups::uuid
|
||||
.eq(collections_groups::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(collections::org_uuid))),
|
||||
)
|
||||
.select(collections_groups::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collection groups")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(&self, org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -428,13 +443,14 @@ impl CollectionGroup {
|
||||
group_user.update_user_revision(conn).await;
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |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")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_group(group_uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -443,12 +459,13 @@ impl CollectionGroup {
|
||||
group_user.update_user_revision(conn).await;
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(collections_groups::table)
|
||||
.filter(collections_groups::groups_uuid.eq(group_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting collection group")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(
|
||||
@@ -464,12 +481,13 @@ impl CollectionGroup {
|
||||
}
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(collections_groups::table)
|
||||
.filter(collections_groups::collections_uuid.eq(collection_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting collection group")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,30 +539,31 @@ impl GroupUser {
|
||||
}
|
||||
|
||||
pub async fn find_by_group(group_uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
groups_users::table
|
||||
.inner_join(groups::table.on(
|
||||
groups::uuid.eq(groups_users::groups_uuid)
|
||||
))
|
||||
.inner_join(users_organizations::table.on(
|
||||
users_organizations::uuid.eq(groups_users::users_organizations_uuid)
|
||||
.and(users_organizations::org_uuid.eq(groups::organizations_uuid))
|
||||
))
|
||||
.inner_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid)))
|
||||
.inner_join(
|
||||
users_organizations::table.on(users_organizations::uuid
|
||||
.eq(groups_users::users_organizations_uuid)
|
||||
.and(users_organizations::org_uuid.eq(groups::organizations_uuid))),
|
||||
)
|
||||
.filter(groups_users::groups_uuid.eq(group_uuid))
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
.select(groups_users::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading group users")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_member(member_uuid: &MembershipId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
groups_users::table
|
||||
.filter(groups_users::users_organizations_uuid.eq(member_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading groups for user")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn has_access_to_collection_by_member(
|
||||
@@ -552,24 +571,23 @@ impl GroupUser {
|
||||
member_uuid: &MembershipId,
|
||||
conn: &DbConn,
|
||||
) -> bool {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
groups_users::table
|
||||
.inner_join(collections_groups::table.on(
|
||||
collections_groups::groups_uuid.eq(groups_users::groups_uuid)
|
||||
))
|
||||
.inner_join(groups::table.on(
|
||||
groups::uuid.eq(groups_users::groups_uuid)
|
||||
))
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(collections_groups::collections_uuid)
|
||||
.and(collections::org_uuid.eq(groups::organizations_uuid))
|
||||
))
|
||||
.inner_join(collections_groups::table.on(collections_groups::groups_uuid.eq(groups_users::groups_uuid)))
|
||||
.inner_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid)))
|
||||
.inner_join(
|
||||
collections::table.on(collections::uuid
|
||||
.eq(collections_groups::collections_uuid)
|
||||
.and(collections::org_uuid.eq(groups::organizations_uuid))),
|
||||
)
|
||||
.filter(collections_groups::collections_uuid.eq(collection_uuid))
|
||||
.filter(groups_users::users_organizations_uuid.eq(member_uuid))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
.unwrap_or(0)
|
||||
!= 0
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn has_full_access_by_member(
|
||||
@@ -577,18 +595,18 @@ impl GroupUser {
|
||||
member_uuid: &MembershipId,
|
||||
conn: &DbConn,
|
||||
) -> bool {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
groups_users::table
|
||||
.inner_join(groups::table.on(
|
||||
groups::uuid.eq(groups_users::groups_uuid)
|
||||
))
|
||||
.inner_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid)))
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
.filter(groups::access_all.eq(true))
|
||||
.filter(groups_users::users_organizations_uuid.eq(member_uuid))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
.unwrap_or(0)
|
||||
!= 0
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_user_revision(&self, conn: &DbConn) {
|
||||
@@ -606,15 +624,16 @@ impl GroupUser {
|
||||
match Membership::find_by_uuid(member_uuid, conn).await {
|
||||
Some(member) => User::update_uuid_revision(&member.user_uuid, conn).await,
|
||||
None => warn!("Member could not be found!"),
|
||||
};
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(groups_users::table)
|
||||
.filter(groups_users::groups_uuid.eq(group_uuid))
|
||||
.filter(groups_users::users_organizations_uuid.eq(member_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting group users")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_group(group_uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -623,12 +642,13 @@ impl GroupUser {
|
||||
group_user.update_user_revision(conn).await;
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(groups_users::table)
|
||||
.filter(groups_users::groups_uuid.eq(group_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting group users")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_member(member_uuid: &MembershipId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -637,12 +657,13 @@ impl GroupUser {
|
||||
None => warn!("Member could not be found!"),
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(groups_users::table)
|
||||
.filter(groups_users::users_organizations_uuid.eq(member_uuid))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting user groups")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ pub use self::attachment::{Attachment, AttachmentId};
|
||||
pub use self::auth_request::{AuthRequest, AuthRequestId};
|
||||
pub use self::cipher::{Cipher, CipherId, RepromptType};
|
||||
pub use self::collection::{Collection, CollectionCipher, CollectionId, CollectionUser};
|
||||
pub use self::device::{Device, DeviceId, DeviceType, PushId};
|
||||
pub use self::device::{Device, DeviceId, DeviceType, DeviceWithAuthRequest, PushId};
|
||||
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessId, EmergencyAccessStatus, EmergencyAccessType};
|
||||
pub use self::event::{Event, EventType};
|
||||
pub use self::favorite::Favorite;
|
||||
@@ -35,8 +35,8 @@ pub use self::organization::{
|
||||
OrganizationId,
|
||||
};
|
||||
pub use self::send::{
|
||||
id::{SendFileId, SendId},
|
||||
Send, SendType,
|
||||
id::{SendFileId, SendId},
|
||||
};
|
||||
pub use self::sso_auth::{OIDCAuthenticatedUser, OIDCCodeResponseError, SsoAuth};
|
||||
pub use self::two_factor::{TwoFactor, TwoFactorType};
|
||||
|
||||
+74
-70
@@ -1,14 +1,17 @@
|
||||
use derive_more::{AsRef, From};
|
||||
use diesel::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::api::core::two_factor;
|
||||
use crate::api::EmptyResult;
|
||||
use crate::db::schema::{org_policies, users_organizations};
|
||||
use crate::db::DbConn;
|
||||
use crate::error::MapResult;
|
||||
use crate::CONFIG;
|
||||
use diesel::prelude::*;
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::{EmptyResult, core::two_factor},
|
||||
db::{
|
||||
DbConn,
|
||||
schema::{org_policies, users_organizations},
|
||||
},
|
||||
error::MapResult,
|
||||
};
|
||||
|
||||
use super::{Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId, TwoFactor, UserId};
|
||||
|
||||
@@ -148,37 +151,38 @@ impl OrgPolicy {
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(org_policies::table.filter(org_policies::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting org_policy")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
org_policies::table
|
||||
.filter(org_policies::org_uuid.eq(org_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading org_policy")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
org_policies::table
|
||||
.inner_join(
|
||||
users_organizations::table.on(
|
||||
users_organizations::org_uuid.eq(org_policies::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid)))
|
||||
)
|
||||
.filter(
|
||||
users_organizations::status.eq(MembershipStatus::Confirmed as i32)
|
||||
users_organizations::table.on(users_organizations::org_uuid
|
||||
.eq(org_policies::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid))),
|
||||
)
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.select(org_policies::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading org_policy")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_org_and_type(
|
||||
@@ -186,21 +190,23 @@ impl OrgPolicy {
|
||||
policy_type: OrgPolicyType,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
org_policies::table
|
||||
.filter(org_policies::org_uuid.eq(org_uuid))
|
||||
.filter(org_policies::atype.eq(policy_type as i32))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting org_policy")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_accepted_and_confirmed_by_user_and_active_policy(
|
||||
@@ -208,25 +214,22 @@ impl OrgPolicy {
|
||||
policy_type: OrgPolicyType,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
org_policies::table
|
||||
.inner_join(
|
||||
users_organizations::table.on(
|
||||
users_organizations::org_uuid.eq(org_policies::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid)))
|
||||
)
|
||||
.filter(
|
||||
users_organizations::status.eq(MembershipStatus::Accepted as i32)
|
||||
)
|
||||
.or_filter(
|
||||
users_organizations::status.eq(MembershipStatus::Confirmed as i32)
|
||||
users_organizations::table.on(users_organizations::org_uuid
|
||||
.eq(org_policies::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid))),
|
||||
)
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Accepted as i32))
|
||||
.or_filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.filter(org_policies::atype.eq(policy_type as i32))
|
||||
.filter(org_policies::enabled.eq(true))
|
||||
.select(org_policies::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading org_policy")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_user_and_active_policy(
|
||||
@@ -234,22 +237,21 @@ impl OrgPolicy {
|
||||
policy_type: OrgPolicyType,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
org_policies::table
|
||||
.inner_join(
|
||||
users_organizations::table.on(
|
||||
users_organizations::org_uuid.eq(org_policies::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid)))
|
||||
)
|
||||
.filter(
|
||||
users_organizations::status.eq(MembershipStatus::Confirmed as i32)
|
||||
users_organizations::table.on(users_organizations::org_uuid
|
||||
.eq(org_policies::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid))),
|
||||
)
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.filter(org_policies::atype.eq(policy_type as i32))
|
||||
.filter(org_policies::enabled.eq(true))
|
||||
.select(org_policies::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading org_policy")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns true if the user belongs to an org that has enabled the specified policy type,
|
||||
@@ -269,10 +271,10 @@ impl OrgPolicy {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(user) = Membership::find_confirmed_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
|
||||
if user.atype < MembershipType::Admin {
|
||||
return true;
|
||||
}
|
||||
if let Some(user) = Membership::find_confirmed_by_user_and_org(user_uuid, &policy.org_uuid, conn).await
|
||||
&& user.atype < MembershipType::Admin
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -282,13 +284,13 @@ impl OrgPolicy {
|
||||
if m.atype < MembershipType::Admin && m.status > (MembershipStatus::Invited as i32) {
|
||||
// Enforce TwoFactor/TwoStep login
|
||||
if let Some(p) = Self::find_by_org_and_type(&m.org_uuid, OrgPolicyType::TwoFactorAuthentication, conn).await
|
||||
&& p.enabled
|
||||
&& TwoFactor::find_by_user(&m.user_uuid, conn).await.is_empty()
|
||||
{
|
||||
if p.enabled && TwoFactor::find_by_user(&m.user_uuid, conn).await.is_empty() {
|
||||
if CONFIG.email_2fa_auto_fallback() {
|
||||
two_factor::email::find_and_activate_email_2fa(&m.user_uuid, conn).await?;
|
||||
} else {
|
||||
err!(format!("Cannot {} because 2FA is required (membership {})", action, m.uuid));
|
||||
}
|
||||
if CONFIG.email_2fa_auto_fallback() {
|
||||
two_factor::email::find_and_activate_email_2fa(&m.user_uuid, conn).await?;
|
||||
} else {
|
||||
err!(format!("Cannot {} because 2FA is required (membership {})", action, m.uuid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,12 +302,14 @@ impl OrgPolicy {
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(p) = Self::find_by_org_and_type(&m.org_uuid, OrgPolicyType::SingleOrg, conn).await {
|
||||
if p.enabled
|
||||
&& Membership::count_accepted_and_confirmed_by_user(&m.user_uuid, &m.org_uuid, conn).await > 0
|
||||
{
|
||||
err!(format!("Cannot {} because the organization policy forbids being part of other organization (membership {})", action, m.uuid));
|
||||
}
|
||||
if let Some(p) = Self::find_by_org_and_type(&m.org_uuid, OrgPolicyType::SingleOrg, conn).await
|
||||
&& p.enabled
|
||||
&& Membership::count_accepted_and_confirmed_by_user(&m.user_uuid, &m.org_uuid, conn).await > 0
|
||||
{
|
||||
err!(format!(
|
||||
"Cannot {} because the organization policy forbids being part of other organization (membership {})",
|
||||
action, m.uuid
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,16 +336,16 @@ impl OrgPolicy {
|
||||
for policy in
|
||||
OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await
|
||||
{
|
||||
if let Some(user) = Membership::find_confirmed_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
|
||||
if user.atype < MembershipType::Admin {
|
||||
match serde_json::from_str::<SendOptionsPolicyData>(&policy.data) {
|
||||
Ok(opts) => {
|
||||
if opts.disable_hide_email {
|
||||
return true;
|
||||
}
|
||||
if let Some(user) = Membership::find_confirmed_by_user_and_org(user_uuid, &policy.org_uuid, conn).await
|
||||
&& user.atype < MembershipType::Admin
|
||||
{
|
||||
match serde_json::from_str::<SendOptionsPolicyData>(&policy.data) {
|
||||
Ok(opts) => {
|
||||
if opts.disable_hide_email {
|
||||
return true;
|
||||
}
|
||||
_ => error!("Failed to deserialize SendOptionsPolicyData: {}", policy.data),
|
||||
}
|
||||
_ => error!("Failed to deserialize SendOptionsPolicyData: {}", policy.data),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,10 +353,10 @@ impl OrgPolicy {
|
||||
}
|
||||
|
||||
pub async fn is_enabled_for_member(member_uuid: &MembershipId, policy_type: OrgPolicyType, conn: &DbConn) -> bool {
|
||||
if let Some(member) = Membership::find_by_uuid(member_uuid, conn).await {
|
||||
if let Some(policy) = OrgPolicy::find_by_org_and_type(&member.org_uuid, policy_type, conn).await {
|
||||
return policy.enabled;
|
||||
}
|
||||
if let Some(member) = Membership::find_by_uuid(member_uuid, conn).await
|
||||
&& let Some(policy) = OrgPolicy::find_by_org_and_type(&member.org_uuid, policy_type, conn).await
|
||||
{
|
||||
return policy.enabled;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
+202
-185
@@ -1,23 +1,32 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use num_traits::FromPrimitive;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{HashMap, HashSet},
|
||||
};
|
||||
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use num_traits::FromPrimitive;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::EmptyResult,
|
||||
db::{
|
||||
DbConn,
|
||||
schema::{
|
||||
ciphers, ciphers_collections, collections_groups, groups, groups_users, org_policies, organization_api_key,
|
||||
organizations, users, users_collections, users_organizations,
|
||||
},
|
||||
},
|
||||
error::MapResult,
|
||||
};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
use super::{
|
||||
CipherId, Collection, CollectionGroup, CollectionId, CollectionUser, Group, GroupId, GroupUser, OrgPolicy,
|
||||
Cipher, CipherId, Collection, CollectionGroup, CollectionId, CollectionUser, Group, GroupId, GroupUser, OrgPolicy,
|
||||
OrgPolicyType, TwoFactor, User, UserId,
|
||||
};
|
||||
use crate::db::schema::{
|
||||
ciphers, ciphers_collections, collections_groups, groups, groups_users, org_policies, organization_api_key,
|
||||
organizations, users, users_collections, users_organizations,
|
||||
};
|
||||
use crate::CONFIG;
|
||||
use macros::UuidFromParam;
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = organizations)]
|
||||
@@ -93,6 +102,10 @@ pub enum MembershipType {
|
||||
|
||||
impl MembershipType {
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
#[expect(
|
||||
clippy::match_same_arms,
|
||||
reason = "Specifically define `4|Custom` since this is a hack, not a default"
|
||||
)]
|
||||
match s {
|
||||
"0" | "Owner" => Some(MembershipType::Owner),
|
||||
"1" | "Admin" => Some(MembershipType::Admin),
|
||||
@@ -321,11 +334,6 @@ impl OrganizationApiKey {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Organization {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
@@ -333,7 +341,7 @@ impl Organization {
|
||||
err!(format!("BillingEmail {} is not a valid email address", self.billing_email))
|
||||
}
|
||||
|
||||
for member in Membership::find_by_org(&self.uuid, conn).await.iter() {
|
||||
for member in &Membership::find_by_org(&self.uuid, conn).await {
|
||||
User::update_uuid_revision(&member.user_uuid, conn).await;
|
||||
}
|
||||
|
||||
@@ -369,8 +377,6 @@ impl Organization {
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
use super::{Cipher, Collection};
|
||||
|
||||
Cipher::delete_all_by_organization(&self.uuid, conn).await?;
|
||||
Collection::delete_all_by_organization(&self.uuid, conn).await?;
|
||||
Membership::delete_all_by_organization(&self.uuid, conn).await?;
|
||||
@@ -378,43 +384,30 @@ impl Organization {
|
||||
Group::delete_all_by_organization(&self.uuid, conn).await?;
|
||||
OrganizationApiKey::delete_all_by_organization(&self.uuid, conn).await?;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error saving organization")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
organizations::table
|
||||
.filter(organizations::uuid.eq(uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| organizations::table.filter(organizations::uuid.eq(uuid)).first::<Self>(conn).ok()).await
|
||||
}
|
||||
|
||||
pub async fn find_by_name(name: &str, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
organizations::table
|
||||
.filter(organizations::name.eq(name))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| organizations::table.filter(organizations::name.eq(name)).first::<Self>(conn).ok()).await
|
||||
}
|
||||
|
||||
pub async fn get_all(conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
organizations::table
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading organizations")
|
||||
}}
|
||||
conn.run(move |conn| organizations::table.load::<Self>(conn).expect("Error loading organizations")).await
|
||||
}
|
||||
|
||||
pub async fn find_main_org_user_email(user_email: &str, conn: &DbConn) -> Option<Self> {
|
||||
let lower_mail = user_email.to_lowercase();
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
organizations::table
|
||||
.inner_join(users_organizations::table.on(users_organizations::org_uuid.eq(organizations::uuid)))
|
||||
.inner_join(users::table.on(users::uuid.eq(users_organizations::user_uuid)))
|
||||
@@ -424,13 +417,14 @@ impl Organization {
|
||||
.select(organizations::all_columns)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_org_user_email(user_email: &str, conn: &DbConn) -> Vec<Self> {
|
||||
let lower_mail = user_email.to_lowercase();
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
organizations::table
|
||||
.inner_join(users_organizations::table.on(users_organizations::org_uuid.eq(organizations::uuid)))
|
||||
.inner_join(users::table.on(users::uuid.eq(users_organizations::user_uuid)))
|
||||
@@ -440,7 +434,8 @@ impl Organization {
|
||||
.select(organizations::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user orgs")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,11 +775,12 @@ impl Membership {
|
||||
CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?;
|
||||
GroupUser::delete_all_by_member(&self.uuid, conn).await?;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing user from organization")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -802,10 +798,10 @@ impl Membership {
|
||||
}
|
||||
|
||||
pub async fn find_by_email_and_org(email: &str, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Membership> {
|
||||
if let Some(user) = User::find_by_mail(email, conn).await {
|
||||
if let Some(member) = Membership::find_by_user_and_org(&user.uuid, org_uuid, conn).await {
|
||||
return Some(member);
|
||||
}
|
||||
if let Some(user) = User::find_by_mail(email, conn).await
|
||||
&& let Some(member) = Membership::find_by_user_and_org(&user.uuid, org_uuid, conn).await
|
||||
{
|
||||
return Some(member);
|
||||
}
|
||||
|
||||
None
|
||||
@@ -824,64 +820,67 @@ impl Membership {
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &MembershipId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::uuid.eq(uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table.filter(users_organizations::uuid.eq(uuid)).first::<Self>(conn).ok()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_org(uuid: &MembershipId, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::uuid.eq(uuid))
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_invited_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Invited as i32))
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// Should be used only when email are disabled.
|
||||
// In Organizations::send_invite status is set to Accepted only if the user has a password.
|
||||
pub async fn accept_user_invitations(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::update(users_organizations::table)
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Invited as i32))
|
||||
.set(users_organizations::status.eq(MembershipStatus::Accepted as i32))
|
||||
.execute(conn)
|
||||
.map_res("Error confirming invitations")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_any_state_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_accepted_and_confirmed_by_user(
|
||||
@@ -889,70 +888,83 @@ impl Membership {
|
||||
excluded_org: &OrganizationId,
|
||||
conn: &DbConn,
|
||||
) -> i64 {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::org_uuid.ne(excluded_org))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Accepted as i32).or(users_organizations::status.eq(MembershipStatus::Confirmed as i32)))
|
||||
.filter(
|
||||
users_organizations::status
|
||||
.eq(MembershipStatus::Accepted as i32)
|
||||
.or(users_organizations::status.eq(MembershipStatus::Confirmed as i32)),
|
||||
)
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// Get all users which are either owner or admin, or a manager which can manage/access all
|
||||
pub async fn find_confirmed_and_manage_all_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.filter(
|
||||
users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32])
|
||||
.or(users_organizations::atype.eq(MembershipType::Manager as i32).and(users_organizations::access_all.eq(true)))
|
||||
users_organizations::atype
|
||||
.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32])
|
||||
.or(users_organizations::atype
|
||||
.eq(MembershipType::Manager as i32)
|
||||
.and(users_organizations::access_all.eq(true))),
|
||||
)
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_org_and_type(org_uuid: &OrganizationId, atype: MembershipType, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.filter(users_organizations::atype.eq(atype as i32))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_confirmed_by_org_and_type(
|
||||
@@ -960,7 +972,7 @@ impl Membership {
|
||||
atype: MembershipType,
|
||||
conn: &DbConn,
|
||||
) -> i64 {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.filter(users_organizations::atype.eq(atype as i32))
|
||||
@@ -968,17 +980,19 @@ impl Membership {
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.unwrap_or(0)
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_org(user_uuid: &UserId, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_user_and_org(
|
||||
@@ -986,78 +1000,76 @@ impl Membership {
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.filter(
|
||||
users_organizations::status.eq(MembershipStatus::Confirmed as i32)
|
||||
)
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_orgs_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<OrganizationId> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.select(users_organizations::org_uuid)
|
||||
.load::<OrganizationId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_policy(user_uuid: &UserId, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.inner_join(
|
||||
org_policies::table.on(
|
||||
org_policies::org_uuid.eq(users_organizations::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid))
|
||||
.and(org_policies::atype.eq(policy_type as i32))
|
||||
.and(org_policies::enabled.eq(true)))
|
||||
)
|
||||
.filter(
|
||||
users_organizations::status.eq(MembershipStatus::Confirmed as i32)
|
||||
org_policies::table.on(org_policies::org_uuid
|
||||
.eq(users_organizations::org_uuid)
|
||||
.and(users_organizations::user_uuid.eq(user_uuid))
|
||||
.and(org_policies::atype.eq(policy_type as i32))
|
||||
.and(org_policies::enabled.eq(true))),
|
||||
)
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.select(users_organizations::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_cipher_and_org(cipher_uuid: &CipherId, org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.left_join(users_collections::table.on(
|
||||
users_collections::user_uuid.eq(users_organizations::user_uuid)
|
||||
))
|
||||
.left_join(ciphers_collections::table.on(
|
||||
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid).and(
|
||||
ciphers_collections::cipher_uuid.eq(&cipher_uuid)
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.left_join(users_collections::table.on(users_collections::user_uuid.eq(users_organizations::user_uuid)))
|
||||
.left_join(
|
||||
ciphers_collections::table.on(ciphers_collections::collection_uuid
|
||||
.eq(users_collections::collection_uuid)
|
||||
.and(ciphers_collections::cipher_uuid.eq(&cipher_uuid))),
|
||||
)
|
||||
))
|
||||
.filter(
|
||||
users_organizations::access_all.eq(true).or( // AccessAll..
|
||||
ciphers_collections::cipher_uuid.eq(&cipher_uuid) // ..or access to collection with cipher
|
||||
)
|
||||
)
|
||||
.select(users_organizations::all_columns)
|
||||
.distinct()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
.filter(users_organizations::access_all.eq(true).or(
|
||||
// AccessAll..
|
||||
ciphers_collections::cipher_uuid.eq(&cipher_uuid), // ..or access to collection with cipher
|
||||
))
|
||||
.select(users_organizations::all_columns)
|
||||
.distinct()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_cipher_and_org_with_group(
|
||||
@@ -1065,45 +1077,54 @@ impl Membership {
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.inner_join(groups_users::table.on(
|
||||
groups_users::users_organizations_uuid.eq(users_organizations::uuid)
|
||||
))
|
||||
.left_join(collections_groups::table.on(
|
||||
collections_groups::groups_uuid.eq(groups_users::groups_uuid)
|
||||
))
|
||||
.left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))
|
||||
))
|
||||
.left_join(ciphers_collections::table.on(
|
||||
ciphers_collections::collection_uuid.eq(collections_groups::collections_uuid).and(ciphers_collections::cipher_uuid.eq(&cipher_uuid))
|
||||
|
||||
))
|
||||
.filter(
|
||||
groups::access_all.eq(true).or( // AccessAll via groups
|
||||
ciphers_collections::cipher_uuid.eq(&cipher_uuid) // ..or access to collection via group
|
||||
)
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.inner_join(
|
||||
groups_users::table.on(groups_users::users_organizations_uuid.eq(users_organizations::uuid)),
|
||||
)
|
||||
.left_join(collections_groups::table.on(collections_groups::groups_uuid.eq(groups_users::groups_uuid)))
|
||||
.left_join(
|
||||
groups::table.on(groups::uuid
|
||||
.eq(groups_users::groups_uuid)
|
||||
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
|
||||
)
|
||||
.left_join(
|
||||
ciphers_collections::table.on(ciphers_collections::collection_uuid
|
||||
.eq(collections_groups::collections_uuid)
|
||||
.and(ciphers_collections::cipher_uuid.eq(&cipher_uuid))),
|
||||
)
|
||||
.filter(groups::access_all.eq(true).or(
|
||||
// AccessAll via groups
|
||||
ciphers_collections::cipher_uuid.eq(&cipher_uuid), // ..or access to collection via group
|
||||
))
|
||||
.select(users_organizations::all_columns)
|
||||
.distinct()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations with groups")
|
||||
}}
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations with groups")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn user_has_ge_admin_access_to_cipher(user_uuid: &UserId, cipher_uuid: &CipherId, conn: &DbConn) -> bool {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.inner_join(ciphers::table.on(ciphers::uuid.eq(cipher_uuid).and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))))
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32]))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
.inner_join(
|
||||
ciphers::table.on(ciphers::uuid
|
||||
.eq(cipher_uuid)
|
||||
.and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))),
|
||||
)
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(
|
||||
users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32]),
|
||||
)
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok()
|
||||
.unwrap_or(0)
|
||||
!= 0
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_collection_and_org(
|
||||
@@ -1111,44 +1132,41 @@ impl Membership {
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.left_join(users_collections::table.on(
|
||||
users_collections::user_uuid.eq(users_organizations::user_uuid)
|
||||
))
|
||||
.filter(
|
||||
users_organizations::access_all.eq(true).or( // AccessAll..
|
||||
users_collections::collection_uuid.eq(&collection_uuid) // ..or access to collection with cipher
|
||||
)
|
||||
)
|
||||
.select(users_organizations::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.left_join(users_collections::table.on(users_collections::user_uuid.eq(users_organizations::user_uuid)))
|
||||
.filter(users_organizations::access_all.eq(true).or(
|
||||
// AccessAll..
|
||||
users_collections::collection_uuid.eq(&collection_uuid), // ..or access to collection with cipher
|
||||
))
|
||||
.select(users_organizations::all_columns)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_external_id_and_org(ext_id: &str, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(
|
||||
users_organizations::external_id.eq(ext_id)
|
||||
.and(users_organizations::org_uuid.eq(org_uuid))
|
||||
)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
.filter(users_organizations::external_id.eq(ext_id).and(users_organizations::org_uuid.eq(org_uuid)))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_main_user_org(user_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::status.ne(MembershipStatus::Revoked as i32))
|
||||
.order(users_organizations::atype.asc())
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1186,20 +1204,19 @@ impl OrganizationApiKey {
|
||||
}
|
||||
|
||||
pub async fn find_by_org_uuid(org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
organization_api_key::table
|
||||
.filter(organization_api_key::org_uuid.eq(org_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
organization_api_key::table.filter(organization_api_key::org_uuid.eq(org_uuid)).first::<Self>(conn).ok()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(organization_api_key::table.filter(organization_api_key::org_uuid.eq(org_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error removing organization api key from organization")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+54
-81
@@ -1,11 +1,19 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{config::PathType, util::LowerCase, CONFIG};
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::EmptyResult,
|
||||
config::PathType,
|
||||
db::{DbConn, schema::sends},
|
||||
error::MapResult,
|
||||
util::{LowerCase, NumberOrString, format_date},
|
||||
};
|
||||
|
||||
use super::{OrganizationId, User, UserId};
|
||||
use crate::db::schema::sends;
|
||||
use diesel::prelude::*;
|
||||
use id::SendId;
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
@@ -107,37 +115,33 @@ impl Send {
|
||||
pub fn check_password(&self, password: &str) -> bool {
|
||||
match (&self.password_hash, &self.password_salt, self.password_iter) {
|
||||
(Some(hash), Some(salt), Some(iter)) => {
|
||||
crate::crypto::verify_password_hash(password.as_bytes(), salt, hash, iter as u32)
|
||||
crate::crypto::verify_password_hash(password.as_bytes(), salt, hash, iter.cast_unsigned())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn creator_identifier(&self, conn: &DbConn) -> Option<String> {
|
||||
if let Some(hide_email) = self.hide_email {
|
||||
if hide_email {
|
||||
return None;
|
||||
}
|
||||
if let Some(hide_email) = self.hide_email
|
||||
&& hide_email
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(user_uuid) = &self.user_uuid {
|
||||
if let Some(user) = User::find_by_uuid(user_uuid, conn).await {
|
||||
return Some(user.email);
|
||||
}
|
||||
if let Some(user_uuid) = &self.user_uuid
|
||||
&& let Some(user) = User::find_by_uuid(user_uuid, conn).await
|
||||
{
|
||||
return Some(user.email);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> Value {
|
||||
use crate::util::format_date;
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
use uuid::Uuid;
|
||||
|
||||
let mut data = serde_json::from_str::<LowerCase<Value>>(&self.data).map(|d| d.data).unwrap_or_default();
|
||||
|
||||
// Mobile clients expect size to be a string instead of a number
|
||||
if let Some(size) = data.get("size").and_then(|v| v.as_i64()) {
|
||||
if let Some(size) = data.get("size").and_then(Value::as_i64) {
|
||||
data["size"] = Value::String(size.to_string());
|
||||
}
|
||||
|
||||
@@ -167,12 +171,10 @@ impl Send {
|
||||
}
|
||||
|
||||
pub async fn to_json_access(&self, conn: &DbConn) -> Value {
|
||||
use crate::util::format_date;
|
||||
|
||||
let mut data = serde_json::from_str::<LowerCase<Value>>(&self.data).map(|d| d.data).unwrap_or_default();
|
||||
|
||||
// Mobile clients expect size to be a string instead of a number
|
||||
if let Some(size) = data.get("size").and_then(|v| v.as_i64()) {
|
||||
if let Some(size) = data.get("size").and_then(Value::as_i64) {
|
||||
data["size"] = Value::String(size.to_string());
|
||||
}
|
||||
|
||||
@@ -191,12 +193,6 @@ impl Send {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
use crate::util::NumberOrString;
|
||||
|
||||
impl Send {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
self.update_users_revision(conn).await;
|
||||
@@ -240,11 +236,10 @@ impl Send {
|
||||
operator.delete_with(&self.uuid).recursive(true).await.ok();
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(sends::table.filter(sends::uuid.eq(&self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting send")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(sends::table.filter(sends::uuid.eq(&self.uuid))).execute(conn).map_res("Error deleting send")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Purge all sends that are past their deletion date.
|
||||
@@ -256,15 +251,12 @@ impl Send {
|
||||
|
||||
pub async fn update_users_revision(&self, conn: &DbConn) -> Vec<UserId> {
|
||||
let mut user_uuids = Vec::new();
|
||||
match &self.user_uuid {
|
||||
Some(user_uuid) => {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
user_uuids.push(user_uuid.clone())
|
||||
}
|
||||
None => {
|
||||
// Belongs to Organization, not implemented
|
||||
}
|
||||
};
|
||||
if let Some(user_uuid) = &self.user_uuid {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
user_uuids.push(user_uuid.clone());
|
||||
} else {
|
||||
// Belongs to Organization, not implemented
|
||||
}
|
||||
user_uuids
|
||||
}
|
||||
|
||||
@@ -276,9 +268,6 @@ impl Send {
|
||||
}
|
||||
|
||||
pub async fn find_by_access_id(access_id: &str, conn: &DbConn) -> Option<Self> {
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
use uuid::Uuid;
|
||||
|
||||
let Ok(uuid_vec) = BASE64URL_NOPAD.decode(access_id.as_bytes()) else {
|
||||
return None;
|
||||
};
|
||||
@@ -292,50 +281,38 @@ impl Send {
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &SendId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
sends::table
|
||||
.filter(sends::uuid.eq(uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| sends::table.filter(sends::uuid.eq(uuid)).first::<Self>(conn).ok()).await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &SendId, user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
sends::table
|
||||
.filter(sends::uuid.eq(uuid))
|
||||
.filter(sends::user_uuid.eq(user_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
sends::table.filter(sends::uuid.eq(uuid)).filter(sends::user_uuid.eq(user_uuid)).first::<Self>(conn).ok()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
sends::table
|
||||
.filter(sends::user_uuid.eq(user_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading sends")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
sends::table.filter(sends::user_uuid.eq(user_uuid)).load::<Self>(conn).expect("Error loading sends")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn size_by_user(user_uuid: &UserId, conn: &DbConn) -> Option<i64> {
|
||||
let sends = Self::find_by_user(user_uuid, conn).await;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct FileData {
|
||||
#[serde(rename = "size", alias = "Size")]
|
||||
size: NumberOrString,
|
||||
}
|
||||
|
||||
let sends = Self::find_by_user(user_uuid, conn).await;
|
||||
let mut total: i64 = 0;
|
||||
for send in sends {
|
||||
if send.atype == SendType::File as i32 {
|
||||
if let Ok(size) =
|
||||
if send.atype == SendType::File as i32
|
||||
&& let Ok(size) =
|
||||
serde_json::from_str::<FileData>(&send.data).map_err(Into::into).and_then(|d| d.size.into_i64())
|
||||
{
|
||||
total = total.checked_add(size)?;
|
||||
};
|
||||
{
|
||||
total = total.checked_add(size)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,22 +320,18 @@ impl Send {
|
||||
}
|
||||
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
sends::table
|
||||
.filter(sends::organization_uuid.eq(org_uuid))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading sends")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
sends::table.filter(sends::organization_uuid.eq(org_uuid)).load::<Self>(conn).expect("Error loading sends")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_past_deletion_date(conn: &DbConn) -> Vec<Self> {
|
||||
let now = Utc::now().naive_utc();
|
||||
db_run! { conn: {
|
||||
sends::table
|
||||
.filter(sends::deletion_date.lt(now))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading sends")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
sends::table.filter(sends::deletion_date.lt(now)).load::<Self>(conn).expect("Error loading sends")
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-17
@@ -1,17 +1,20 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::db::schema::sso_auth;
|
||||
use crate::db::{DbConn, DbPool};
|
||||
use crate::error::MapResult;
|
||||
use crate::sso::{OIDCCode, OIDCCodeChallenge, OIDCIdentifier, OIDCState, SSO_AUTH_EXPIRATION};
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use diesel::{
|
||||
deserialize::FromSql,
|
||||
expression::AsExpression,
|
||||
prelude::*,
|
||||
serialize::{Output, ToSql},
|
||||
sql_types::Text,
|
||||
};
|
||||
|
||||
use diesel::deserialize::FromSql;
|
||||
use diesel::expression::AsExpression;
|
||||
use diesel::prelude::*;
|
||||
use diesel::serialize::{Output, ToSql};
|
||||
use diesel::sql_types::Text;
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
db::{DbConn, DbPool, schema::sso_auth},
|
||||
error::MapResult,
|
||||
sso::{OIDCCode, OIDCCodeChallenge, OIDCIdentifier, OIDCState, SSO_AUTH_EXPIRATION},
|
||||
};
|
||||
|
||||
#[derive(AsExpression, Clone, Debug, Serialize, Deserialize, FromSqlRow)]
|
||||
#[diesel(sql_type = Text)]
|
||||
@@ -106,13 +109,14 @@ impl SsoAuth {
|
||||
|
||||
pub async fn find(state: &OIDCState, conn: &DbConn) -> Option<Self> {
|
||||
let oldest = Utc::now().naive_utc() - *SSO_AUTH_EXPIRATION;
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
sso_auth::table
|
||||
.filter(sso_auth::state.eq(state))
|
||||
.filter(sso_auth::created_at.ge(oldest))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_code(code: &OIDCCode, conn: &DbConn) -> Option<Self> {
|
||||
@@ -127,22 +131,24 @@ impl SsoAuth {
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! {conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(sso_auth::table.filter(sso_auth::state.eq(self.state)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting sso_auth")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_expired(pool: DbPool) -> EmptyResult {
|
||||
debug!("Purging expired sso_auth");
|
||||
if let Ok(conn) = pool.get().await {
|
||||
let oldest = Utc::now().naive_utc() - *SSO_AUTH_EXPIRATION;
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(sso_auth::table.filter(sso_auth::created_at.lt(oldest)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting expired SSO nonce")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
err!("Failed to get DB connection while purging expired sso_auth")
|
||||
}
|
||||
|
||||
+39
-28
@@ -1,13 +1,17 @@
|
||||
use super::UserId;
|
||||
use crate::api::core::two_factor::webauthn::WebauthnRegistration;
|
||||
use crate::db::schema::twofactor;
|
||||
use crate::{api::EmptyResult, db::DbConn, error::MapResult};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
use webauthn_rs::prelude::{Credential, ParsedAttestation};
|
||||
use webauthn_rs_core::proto::CredentialV3;
|
||||
use webauthn_rs_proto::{AttestationFormat, RegisteredExtensions};
|
||||
|
||||
use crate::{
|
||||
api::{EmptyResult, core::two_factor::webauthn::WebauthnRegistration},
|
||||
db::{DbConn, schema::twofactor},
|
||||
error::MapResult,
|
||||
};
|
||||
|
||||
use super::UserId;
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = twofactor)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
@@ -114,54 +118,59 @@ impl TwoFactor {
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(twofactor::table.filter(twofactor::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting twofactor")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
twofactor::table
|
||||
.filter(twofactor::user_uuid.eq(user_uuid))
|
||||
.filter(twofactor::atype.lt(1000)) // Filter implementation types
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_type(user_uuid: &UserId, atype: i32, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
twofactor::table
|
||||
.filter(twofactor::user_uuid.eq(user_uuid))
|
||||
.filter(twofactor::atype.eq(atype))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting twofactors")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn migrate_u2f_to_webauthn(conn: &DbConn) -> EmptyResult {
|
||||
let u2f_factors = db_run! { conn: {
|
||||
twofactor::table
|
||||
.filter(twofactor::atype.eq(TwoFactorType::U2f as i32))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor")
|
||||
}};
|
||||
|
||||
use crate::api::core::two_factor::webauthn::U2FRegistration;
|
||||
use crate::api::core::two_factor::webauthn::{get_webauthn_registrations, WebauthnRegistration};
|
||||
use crate::api::core::two_factor::webauthn::{U2FRegistration, get_webauthn_registrations};
|
||||
use webauthn_rs::prelude::{COSEEC2Key, COSEKey, COSEKeyType, ECDSACurve};
|
||||
use webauthn_rs_proto::{COSEAlgorithm, UserVerificationPolicy};
|
||||
|
||||
let u2f_factors = conn
|
||||
.run(move |conn| {
|
||||
twofactor::table
|
||||
.filter(twofactor::atype.eq(TwoFactorType::U2f as i32))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor")
|
||||
})
|
||||
.await;
|
||||
|
||||
for mut u2f in u2f_factors {
|
||||
let mut regs: Vec<U2FRegistration> = serde_json::from_str(&u2f.data)?;
|
||||
// If there are no registrations or they are migrated (we do the migration in batch so we can consider them all migrated when the first one is)
|
||||
@@ -227,12 +236,14 @@ impl TwoFactor {
|
||||
}
|
||||
|
||||
pub async fn migrate_credential_to_passkey(conn: &DbConn) -> EmptyResult {
|
||||
let webauthn_factors = db_run! { conn: {
|
||||
twofactor::table
|
||||
.filter(twofactor::atype.eq(TwoFactorType::Webauthn as i32))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor")
|
||||
}};
|
||||
let webauthn_factors = conn
|
||||
.run(move |conn| {
|
||||
twofactor::table
|
||||
.filter(twofactor::atype.eq(TwoFactorType::Webauthn as i32))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor")
|
||||
})
|
||||
.await;
|
||||
|
||||
for webauthn_factor in webauthn_factors {
|
||||
// assume that a failure to parse into the old struct, means that it was already converted
|
||||
@@ -241,7 +252,7 @@ impl TwoFactor {
|
||||
continue;
|
||||
};
|
||||
|
||||
let regs = regs.into_iter().map(|r| r.into()).collect::<Vec<WebauthnRegistration>>();
|
||||
let regs = regs.into_iter().map(Into::into).collect::<Vec<WebauthnRegistration>>();
|
||||
|
||||
TwoFactor::new(webauthn_factor.user_uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(®s)?)
|
||||
.save(conn)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use chrono::Utc;
|
||||
|
||||
use crate::db::schema::twofactor_duo_ctx;
|
||||
use crate::{api::EmptyResult, db::DbConn, error::MapResult};
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
db::{DbConn, schema::twofactor_duo_ctx},
|
||||
error::MapResult,
|
||||
};
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = twofactor_duo_ctx)]
|
||||
#[diesel(primary_key(state))]
|
||||
@@ -16,12 +19,10 @@ pub struct TwoFactorDuoContext {
|
||||
|
||||
impl TwoFactorDuoContext {
|
||||
pub async fn find_by_state(state: &str, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
twofactor_duo_ctx::table
|
||||
.filter(twofactor_duo_ctx::state.eq(state))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
twofactor_duo_ctx::table.filter(twofactor_duo_ctx::state.eq(state)).first::<Self>(conn).ok()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn save(state: &str, user_email: &str, nonce: &str, ttl: i64, conn: &DbConn) -> EmptyResult {
|
||||
@@ -29,41 +30,42 @@ impl TwoFactorDuoContext {
|
||||
let exists = Self::find_by_state(state, conn).await;
|
||||
if exists.is_some() {
|
||||
return Ok(());
|
||||
};
|
||||
}
|
||||
|
||||
let exp = Utc::now().timestamp() + ttl;
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::insert_into(twofactor_duo_ctx::table)
|
||||
.values((
|
||||
twofactor_duo_ctx::state.eq(state),
|
||||
twofactor_duo_ctx::user_email.eq(user_email),
|
||||
twofactor_duo_ctx::nonce.eq(nonce),
|
||||
twofactor_duo_ctx::exp.eq(exp)
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error saving context to twofactor_duo_ctx")
|
||||
}}
|
||||
twofactor_duo_ctx::exp.eq(exp),
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error saving context to twofactor_duo_ctx")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_expired(conn: &DbConn) -> Vec<Self> {
|
||||
let now = Utc::now().timestamp();
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
twofactor_duo_ctx::table
|
||||
.filter(twofactor_duo_ctx::exp.lt(now))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error finding expired contexts in twofactor_duo_ctx")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(
|
||||
twofactor_duo_ctx::table
|
||||
.filter(twofactor_duo_ctx::state.eq(&self.state)))
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(twofactor_duo_ctx::table.filter(twofactor_duo_ctx::state.eq(&self.state)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting from twofactor_duo_ctx")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn purge_expired_duo_contexts(conn: &DbConn) {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::db::schema::twofactor_incomplete;
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::EmptyResult,
|
||||
auth::ClientIp,
|
||||
db::{
|
||||
models::{DeviceId, UserId},
|
||||
DbConn,
|
||||
models::{DeviceId, UserId},
|
||||
schema::twofactor_incomplete,
|
||||
},
|
||||
error::MapResult,
|
||||
CONFIG,
|
||||
};
|
||||
use diesel::prelude::*;
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = twofactor_incomplete)]
|
||||
@@ -49,7 +49,7 @@ impl TwoFactorIncomplete {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::insert_into(twofactor_incomplete::table)
|
||||
.values((
|
||||
twofactor_incomplete::user_uuid.eq(user_uuid),
|
||||
@@ -61,7 +61,8 @@ impl TwoFactorIncomplete {
|
||||
))
|
||||
.execute(conn)
|
||||
.map_res("Error adding twofactor_incomplete record")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn mark_complete(user_uuid: &UserId, device_uuid: &DeviceId, conn: &DbConn) -> EmptyResult {
|
||||
@@ -73,22 +74,24 @@ impl TwoFactorIncomplete {
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_device(user_uuid: &UserId, device_uuid: &DeviceId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
twofactor_incomplete::table
|
||||
.filter(twofactor_incomplete::user_uuid.eq(user_uuid))
|
||||
.filter(twofactor_incomplete::device_uuid.eq(device_uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_logins_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
twofactor_incomplete::table
|
||||
.filter(twofactor_incomplete::login_time.lt(dt))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor_incomplete")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
@@ -96,20 +99,24 @@ impl TwoFactorIncomplete {
|
||||
}
|
||||
|
||||
pub async fn delete_by_user_and_device(user_uuid: &UserId, device_uuid: &DeviceId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(twofactor_incomplete::table
|
||||
.filter(twofactor_incomplete::user_uuid.eq(user_uuid))
|
||||
.filter(twofactor_incomplete::device_uuid.eq(device_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error in twofactor_incomplete::delete_by_user_and_device()")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(
|
||||
twofactor_incomplete::table
|
||||
.filter(twofactor_incomplete::user_uuid.eq(user_uuid))
|
||||
.filter(twofactor_incomplete::device_uuid.eq(device_uuid)),
|
||||
)
|
||||
.execute(conn)
|
||||
.map_res("Error in twofactor_incomplete::delete_by_user_and_device()")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(twofactor_incomplete::table.filter(twofactor_incomplete::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error in twofactor_incomplete::delete_all_by_user()")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
+71
-72
@@ -1,23 +1,27 @@
|
||||
use crate::db::schema::{invitations, sso_users, twofactor_incomplete, users};
|
||||
use chrono::{NaiveDateTime, TimeDelta, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{
|
||||
Cipher, Device, EmergencyAccess, Favorite, Folder, Membership, MembershipType, TwoFactor, TwoFactorIncomplete,
|
||||
};
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::EmptyResult,
|
||||
crypto,
|
||||
db::{models::DeviceId, DbConn},
|
||||
db::{
|
||||
DbConn,
|
||||
models::DeviceId,
|
||||
schema::{invitations, sso_users, twofactor_incomplete, users},
|
||||
},
|
||||
error::MapResult,
|
||||
sso::OIDCIdentifier,
|
||||
util::{format_date, get_uuid, retry},
|
||||
CONFIG,
|
||||
};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
use super::{
|
||||
Cipher, Device, EmergencyAccess, Favorite, Folder, Membership, MembershipType, TwoFactor, TwoFactorIncomplete,
|
||||
};
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Selectable)]
|
||||
#[diesel(table_name = users)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
@@ -137,8 +141,8 @@ impl User {
|
||||
_totp_secret: None,
|
||||
totp_recover: None,
|
||||
|
||||
equivalent_domains: "[]".to_string(),
|
||||
excluded_globals: "[]".to_string(),
|
||||
equivalent_domains: "[]".to_owned(),
|
||||
excluded_globals: "[]".to_owned(),
|
||||
|
||||
client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT,
|
||||
client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
|
||||
@@ -158,7 +162,7 @@ impl User {
|
||||
password.as_bytes(),
|
||||
&self.salt,
|
||||
&self.password_hash,
|
||||
self.password_iterations as u32,
|
||||
self.password_iterations.cast_unsigned(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -193,7 +197,8 @@ impl User {
|
||||
allow_next_route: Option<Vec<String>>,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
self.password_hash = crypto::hash_password(password.as_bytes(), &self.salt, self.password_iterations as u32);
|
||||
self.password_hash =
|
||||
crypto::hash_password(password.as_bytes(), &self.salt, self.password_iterations.cast_unsigned());
|
||||
|
||||
if let Some(route) = allow_next_route {
|
||||
self.set_stamp_exception(route);
|
||||
@@ -238,10 +243,10 @@ impl User {
|
||||
|
||||
pub fn display_name(&self) -> &str {
|
||||
// default to email if name is empty
|
||||
if !&self.name.is_empty() {
|
||||
&self.name
|
||||
} else {
|
||||
if self.name.is_empty() {
|
||||
&self.email
|
||||
} else {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,15 +342,14 @@ impl User {
|
||||
TwoFactorIncomplete::delete_all_by_user(&self.uuid, conn).await?;
|
||||
Invitation::take(&self.email, conn).await; // Delete invitation if any
|
||||
|
||||
db_run! { conn: {
|
||||
diesel::delete(users::table.filter(users::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting user")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(users::table.filter(users::uuid.eq(self.uuid))).execute(conn).map_res("Error deleting user")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_uuid_revision(uuid: &UserId, conn: &DbConn) {
|
||||
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
|
||||
if let Err(e) = Self::update_revision_impl(uuid, &Utc::now().naive_utc(), conn).await {
|
||||
warn!("Failed to update revision for {uuid}: {e:#?}");
|
||||
}
|
||||
}
|
||||
@@ -353,68 +357,62 @@ impl User {
|
||||
pub async fn update_all_revisions(conn: &DbConn) -> EmptyResult {
|
||||
let updated_at = Utc::now().naive_utc();
|
||||
|
||||
db_run! { conn: {
|
||||
retry(|| {
|
||||
diesel::update(users::table)
|
||||
.set(users::updated_at.eq(updated_at))
|
||||
.execute(conn)
|
||||
}, 10)
|
||||
.map_res("Error updating revision date for all users")
|
||||
}}
|
||||
conn.run(move |conn| {
|
||||
retry(|| diesel::update(users::table).set(users::updated_at.eq(updated_at)).execute(conn), 10)
|
||||
.map_res("Error updating revision date for all users")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_revision(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
self.updated_at = Utc::now().naive_utc();
|
||||
|
||||
Self::_update_revision(&self.uuid, &self.updated_at, conn).await
|
||||
Self::update_revision_impl(&self.uuid, &self.updated_at, conn).await
|
||||
}
|
||||
|
||||
async fn _update_revision(uuid: &UserId, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
retry(|| {
|
||||
diesel::update(users::table.filter(users::uuid.eq(uuid)))
|
||||
.set(users::updated_at.eq(date))
|
||||
.execute(conn)
|
||||
}, 10)
|
||||
async fn update_revision_impl(uuid: &UserId, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
|
||||
conn.run(move |conn| {
|
||||
retry(
|
||||
|| {
|
||||
diesel::update(users::table.filter(users::uuid.eq(uuid)))
|
||||
.set(users::updated_at.eq(date))
|
||||
.execute(conn)
|
||||
},
|
||||
10,
|
||||
)
|
||||
.map_res("Error updating user revision")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
|
||||
let lower_mail = mail.to_lowercase();
|
||||
db_run! { conn: {
|
||||
users::table
|
||||
.filter(users::email.eq(lower_mail))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| users::table.filter(users::email.eq(lower_mail)).first::<Self>(conn).ok()).await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
users::table
|
||||
.filter(users::uuid.eq(uuid))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| users::table.filter(users::uuid.eq(uuid)).first::<Self>(conn).ok()).await
|
||||
}
|
||||
|
||||
pub async fn find_by_device_for_email2fa(device_uuid: &DeviceId, conn: &DbConn) -> Option<Self> {
|
||||
if let Some(user_uuid) = db_run! ( conn: {
|
||||
twofactor_incomplete::table
|
||||
.filter(twofactor_incomplete::device_uuid.eq(device_uuid))
|
||||
.order_by(twofactor_incomplete::login_time.desc())
|
||||
.select(twofactor_incomplete::user_uuid)
|
||||
.first::<UserId>(conn)
|
||||
.ok()
|
||||
}) {
|
||||
if let Some(user_uuid) = conn
|
||||
.run(move |conn| {
|
||||
twofactor_incomplete::table
|
||||
.filter(twofactor_incomplete::device_uuid.eq(device_uuid))
|
||||
.order_by(twofactor_incomplete::login_time.desc())
|
||||
.select(twofactor_incomplete::user_uuid)
|
||||
.first::<UserId>(conn)
|
||||
.ok()
|
||||
})
|
||||
.await
|
||||
{
|
||||
return Self::find_by_uuid(&user_uuid, conn).await;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn get_all(conn: &DbConn) -> Vec<(Self, Option<SsoUser>)> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users::table
|
||||
.left_join(sso_users::table)
|
||||
.select(<(Self, Option<SsoUser>)>::as_select())
|
||||
@@ -422,7 +420,8 @@ impl User {
|
||||
.expect("Error loading groups for user")
|
||||
.into_iter()
|
||||
.collect()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn last_active(&self, conn: &DbConn) -> Option<NaiveDateTime> {
|
||||
@@ -467,21 +466,18 @@ impl Invitation {
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(invitations::table.filter(invitations::email.eq(self.email)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting invitation")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
|
||||
let lower_mail = mail.to_lowercase();
|
||||
db_run! { conn: {
|
||||
invitations::table
|
||||
.filter(invitations::email.eq(lower_mail))
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
conn.run(move |conn| invitations::table.filter(invitations::email.eq(lower_mail)).first::<Self>(conn).ok())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn take(mail: &str, conn: &DbConn) -> bool {
|
||||
@@ -531,34 +527,37 @@ impl SsoUser {
|
||||
}
|
||||
|
||||
pub async fn find_by_identifier(identifier: &str, conn: &DbConn) -> Option<(User, Self)> {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users::table
|
||||
.inner_join(sso_users::table)
|
||||
.select(<(User, Self)>::as_select())
|
||||
.filter(sso_users::identifier.eq(identifier))
|
||||
.first::<(User, Self)>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<(User, Option<Self>)> {
|
||||
let lower_mail = mail.to_lowercase();
|
||||
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
users::table
|
||||
.left_join(sso_users::table)
|
||||
.select(<(User, Option<Self>)>::as_select())
|
||||
.filter(users::email.eq(lower_mail))
|
||||
.first::<(User, Option<Self>)>(conn)
|
||||
.ok()
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
conn.run(move |conn| {
|
||||
diesel::delete(sso_users::table.filter(sso_users::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting sso user")
|
||||
}}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use diesel::connection::{Instrumentation, InstrumentationEvent};
|
||||
use std::{cell::RefCell, collections::HashMap, time::Instant};
|
||||
|
||||
use diesel::connection::{Instrumentation, InstrumentationEvent};
|
||||
|
||||
thread_local! {
|
||||
static QUERY_PERF_TRACKER: RefCell<HashMap<String, Instant>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
@@ -11,7 +12,7 @@ pub fn simple_logger() -> Option<Box<dyn Instrumentation>> {
|
||||
url,
|
||||
..
|
||||
} => {
|
||||
debug!("Establishing connection: {url}")
|
||||
debug!("Establishing connection: {url}");
|
||||
}
|
||||
InstrumentationEvent::FinishEstablishConnection {
|
||||
url,
|
||||
@@ -19,9 +20,9 @@ pub fn simple_logger() -> Option<Box<dyn Instrumentation>> {
|
||||
..
|
||||
} => {
|
||||
if let Some(e) = error {
|
||||
error!("Error during establishing a connection with {url}: {e:?}")
|
||||
error!("Error during establishing a connection with {url}: {e:?}");
|
||||
} else {
|
||||
debug!("Connection established: {url}")
|
||||
debug!("Connection established: {url}");
|
||||
}
|
||||
}
|
||||
InstrumentationEvent::StartQuery {
|
||||
@@ -47,7 +48,7 @@ pub fn simple_logger() -> Option<Box<dyn Instrumentation>> {
|
||||
} else if duration.as_secs() >= 1 {
|
||||
info!("SLOW QUERY [{:.2}s]: {}", duration.as_secs_f32(), query_string);
|
||||
} else {
|
||||
debug!("QUERY [{:?}]: {}", duration, query_string);
|
||||
debug!("QUERY [{duration:?}]: {query_string}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user