mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-11-26 06:32:34 +02:00
Use Diesels MultiConnections Derive (#6279)
* Use Diesels MultiConnections Derive With this PR we remove almost all custom macro's to create the multiple database type code. This is now handled by Diesel it self. This removed the need of the following functions/macro's: - `db_object!` - `::to_db` - `.from_db()` It is also possible to just use one schema instead of multiple per type. Also done: - Refactored the SQLite backup function - Some formatting of queries so every call is one a separate line, this looks a bit better - Declare `conn` as mut inside each `db_run!` instead of having to declare it as `mut` in functions or calls - Added an `ACTIVE_DB_TYPE` static which holds the currently active database type - Removed `diesel_logger` crate and use Diesel's `set_default_instrumentation()` If you want debug queries you can now simply change the log level of `vaultwarden::db::query_logger` - Use PostgreSQL v17 in the Alpine images to match the Debian Trixie version - Optimized the Workflows since `diesel_logger` isn't needed anymore And on the extra plus-side, this lowers the compile-time and binary size too. Signed-off-by: BlackDex <black.dex@gmail.com> * Adjust query_logger and some other small items Signed-off-by: BlackDex <black.dex@gmail.com> * Remove macro, replaced with an function Signed-off-by: BlackDex <black.dex@gmail.com> * Implement custom connection manager Signed-off-by: BlackDex <black.dex@gmail.com> * Updated some crates to keep up2date Signed-off-by: BlackDex <black.dex@gmail.com> * Small adjustment Signed-off-by: BlackDex <black.dex@gmail.com> * crate updates Signed-off-by: BlackDex <black.dex@gmail.com> * Update crates 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
7c597e88f9
commit
2ee5819b56
@@ -1,25 +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 super::{CipherId, OrganizationId, UserId};
|
||||
use crate::db::schema::{attachments, ciphers};
|
||||
use crate::{config::PathType, CONFIG};
|
||||
use macros::IdFromParam;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = attachments)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(id))]
|
||||
pub struct Attachment {
|
||||
pub id: AttachmentId,
|
||||
pub cipher_uuid: CipherId,
|
||||
pub file_name: String, // encrypted
|
||||
pub file_size: i64,
|
||||
pub akey: Option<String>,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = attachments)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(id))]
|
||||
pub struct Attachment {
|
||||
pub id: AttachmentId,
|
||||
pub cipher_uuid: CipherId,
|
||||
pub file_name: String, // encrypted
|
||||
pub file_size: i64,
|
||||
pub akey: Option<String>,
|
||||
}
|
||||
|
||||
/// Local methods
|
||||
@@ -76,11 +75,11 @@ use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Attachment {
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(attachments::table)
|
||||
.values(AttachmentDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -88,7 +87,7 @@ impl Attachment {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(attachments::table)
|
||||
.filter(attachments::id.eq(&self.id))
|
||||
.set(AttachmentDb::to_db(self))
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving attachment")
|
||||
}
|
||||
@@ -96,22 +95,22 @@ impl Attachment {
|
||||
}.map_res("Error saving attachment")
|
||||
}
|
||||
postgresql {
|
||||
let value = AttachmentDb::to_db(self);
|
||||
diesel::insert_into(attachments::table)
|
||||
.values(&value)
|
||||
.values(self)
|
||||
.on_conflict(attachments::id)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving attachment")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
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),
|
||||
crate::util::retry(||
|
||||
diesel::delete(attachments::table.filter(attachments::id.eq(&self.id)))
|
||||
.execute(conn),
|
||||
10,
|
||||
)
|
||||
.map(|_| ())
|
||||
@@ -132,34 +131,32 @@ impl Attachment {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
|
||||
for attachment in Attachment::find_by_cipher(cipher_uuid, conn).await {
|
||||
attachment.delete(conn).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_id(id: &AttachmentId, conn: &mut DbConn) -> Option<Self> {
|
||||
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::<AttachmentDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_cipher(cipher_uuid: &CipherId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
attachments::table
|
||||
.filter(attachments::cipher_uuid.eq(cipher_uuid))
|
||||
.load::<AttachmentDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading attachments")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn size_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 {
|
||||
pub async fn size_by_user(user_uuid: &UserId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
let result: Option<BigDecimal> = attachments::table
|
||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||
@@ -176,7 +173,7 @@ impl Attachment {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 {
|
||||
pub async fn count_by_user(user_uuid: &UserId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
attachments::table
|
||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||
@@ -187,7 +184,7 @@ impl Attachment {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn size_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
|
||||
pub async fn size_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
let result: Option<BigDecimal> = attachments::table
|
||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||
@@ -204,7 +201,7 @@ impl Attachment {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
attachments::table
|
||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||
@@ -221,7 +218,7 @@ impl Attachment {
|
||||
pub async fn find_all_by_user_and_orgs(
|
||||
user_uuid: &UserId,
|
||||
org_uuids: &Vec<OrganizationId>,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
attachments::table
|
||||
@@ -229,9 +226,8 @@ impl Attachment {
|
||||
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||
.or_filter(ciphers::organization_uuid.eq_any(org_uuids))
|
||||
.select(attachments::all_columns)
|
||||
.load::<AttachmentDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading attachments")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
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;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
|
||||
#[diesel(table_name = auth_requests)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct AuthRequest {
|
||||
pub uuid: AuthRequestId,
|
||||
pub user_uuid: UserId,
|
||||
pub organization_uuid: Option<OrganizationId>,
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
|
||||
#[diesel(table_name = auth_requests)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct AuthRequest {
|
||||
pub uuid: AuthRequestId,
|
||||
pub user_uuid: UserId,
|
||||
pub organization_uuid: Option<OrganizationId>,
|
||||
|
||||
pub request_device_identifier: DeviceId,
|
||||
pub device_type: i32, // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/Enums/DeviceType.cs
|
||||
pub request_device_identifier: DeviceId,
|
||||
pub device_type: i32, // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/Enums/DeviceType.cs
|
||||
|
||||
pub request_ip: String,
|
||||
pub response_device_id: Option<DeviceId>,
|
||||
pub request_ip: String,
|
||||
pub response_device_id: Option<DeviceId>,
|
||||
|
||||
pub access_code: String,
|
||||
pub public_key: String,
|
||||
pub access_code: String,
|
||||
pub public_key: String,
|
||||
|
||||
pub enc_key: Option<String>,
|
||||
pub enc_key: Option<String>,
|
||||
|
||||
pub master_password_hash: Option<String>,
|
||||
pub approved: Option<bool>,
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub response_date: Option<NaiveDateTime>,
|
||||
pub master_password_hash: Option<String>,
|
||||
pub approved: Option<bool>,
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub response_date: Option<NaiveDateTime>,
|
||||
|
||||
pub authentication_date: Option<NaiveDateTime>,
|
||||
}
|
||||
pub authentication_date: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
impl AuthRequest {
|
||||
@@ -80,11 +80,11 @@ use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
impl AuthRequest {
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(auth_requests::table)
|
||||
.values(AuthRequestDb::to_db(self))
|
||||
.values(&*self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -92,7 +92,7 @@ impl AuthRequest {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(auth_requests::table)
|
||||
.filter(auth_requests::uuid.eq(&self.uuid))
|
||||
.set(AuthRequestDb::to_db(self))
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error auth_request")
|
||||
}
|
||||
@@ -100,71 +100,71 @@ impl AuthRequest {
|
||||
}.map_res("Error auth_request")
|
||||
}
|
||||
postgresql {
|
||||
let value = AuthRequestDb::to_db(self);
|
||||
diesel::insert_into(auth_requests::table)
|
||||
.values(&value)
|
||||
.values(&*self)
|
||||
.on_conflict(auth_requests::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving auth_request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &AuthRequestId, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
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::<AuthRequestDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &AuthRequestId, user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_by_uuid_and_user(uuid: &AuthRequestId, user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::uuid.eq(uuid))
|
||||
.filter(auth_requests::user_uuid.eq(user_uuid))
|
||||
.first::<AuthRequestDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::user_uuid.eq(user_uuid))
|
||||
.load::<AuthRequestDb>(conn).expect("Error loading auth_requests").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading auth_requests")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_requested_device(
|
||||
user_uuid: &UserId,
|
||||
device_uuid: &DeviceId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::user_uuid.eq(user_uuid))
|
||||
.filter(auth_requests::request_device_identifier.eq(device_uuid))
|
||||
.filter(auth_requests::approved.is_null())
|
||||
.order_by(auth_requests::creation_date.desc())
|
||||
.first::<AuthRequestDb>(conn).ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_created_before(dt: &NaiveDateTime, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_created_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::creation_date.lt(dt))
|
||||
.load::<AuthRequestDb>(conn).expect("Error loading auth_requests").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading auth_requests")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(auth_requests::table.filter(auth_requests::uuid.eq(&self.uuid)))
|
||||
.execute(conn)
|
||||
@@ -176,7 +176,7 @@ impl AuthRequest {
|
||||
ct_eq(&self.access_code, access_code)
|
||||
}
|
||||
|
||||
pub async fn purge_expired_auth_requests(conn: &mut DbConn) {
|
||||
pub async fn purge_expired_auth_requests(conn: &DbConn) {
|
||||
let expiry_time = Utc::now().naive_utc() - chrono::TimeDelta::try_minutes(5).unwrap(); //after 5 minutes, clients reject the request
|
||||
for auth_request in Self::find_created_before(&expiry_time, conn).await {
|
||||
auth_request.delete(conn).await.ok();
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
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 chrono::{NaiveDateTime, TimeDelta, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{
|
||||
@@ -13,39 +18,37 @@ use macros::UuidFromParam;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = ciphers)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Cipher {
|
||||
pub uuid: CipherId,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = ciphers)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Cipher {
|
||||
pub uuid: CipherId,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
|
||||
pub user_uuid: Option<UserId>,
|
||||
pub organization_uuid: Option<OrganizationId>,
|
||||
pub user_uuid: Option<UserId>,
|
||||
pub organization_uuid: Option<OrganizationId>,
|
||||
|
||||
pub key: Option<String>,
|
||||
pub key: Option<String>,
|
||||
|
||||
/*
|
||||
Login = 1,
|
||||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4,
|
||||
SshKey = 5
|
||||
*/
|
||||
pub atype: i32,
|
||||
pub name: String,
|
||||
pub notes: Option<String>,
|
||||
pub fields: Option<String>,
|
||||
/*
|
||||
Login = 1,
|
||||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4,
|
||||
SshKey = 5
|
||||
*/
|
||||
pub atype: i32,
|
||||
pub name: String,
|
||||
pub notes: Option<String>,
|
||||
pub fields: Option<String>,
|
||||
|
||||
pub data: String,
|
||||
pub data: String,
|
||||
|
||||
pub password_history: Option<String>,
|
||||
pub deleted_at: Option<NaiveDateTime>,
|
||||
pub reprompt: Option<i32>,
|
||||
}
|
||||
pub password_history: Option<String>,
|
||||
pub deleted_at: Option<NaiveDateTime>,
|
||||
pub reprompt: Option<i32>,
|
||||
}
|
||||
|
||||
pub enum RepromptType {
|
||||
@@ -140,7 +143,7 @@ impl Cipher {
|
||||
user_uuid: &UserId,
|
||||
cipher_sync_data: Option<&CipherSyncData>,
|
||||
sync_type: CipherSyncType,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Result<Value, crate::Error> {
|
||||
use crate::util::{format_date, validate_and_format_date};
|
||||
|
||||
@@ -402,7 +405,7 @@ impl Cipher {
|
||||
Ok(json_object)
|
||||
}
|
||||
|
||||
pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec<UserId> {
|
||||
pub async fn update_users_revision(&self, conn: &DbConn) -> Vec<UserId> {
|
||||
let mut user_uuids = Vec::new();
|
||||
match self.user_uuid {
|
||||
Some(ref user_uuid) => {
|
||||
@@ -430,14 +433,14 @@ impl Cipher {
|
||||
user_uuids
|
||||
}
|
||||
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
self.update_users_revision(conn).await;
|
||||
self.updated_at = Utc::now().naive_utc();
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(ciphers::table)
|
||||
.values(CipherDb::to_db(self))
|
||||
.values(&*self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -445,7 +448,7 @@ impl Cipher {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(ciphers::table)
|
||||
.filter(ciphers::uuid.eq(&self.uuid))
|
||||
.set(CipherDb::to_db(self))
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving cipher")
|
||||
}
|
||||
@@ -453,19 +456,18 @@ impl Cipher {
|
||||
}.map_res("Error saving cipher")
|
||||
}
|
||||
postgresql {
|
||||
let value = CipherDb::to_db(self);
|
||||
diesel::insert_into(ciphers::table)
|
||||
.values(&value)
|
||||
.values(&*self)
|
||||
.on_conflict(ciphers::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving cipher")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
self.update_users_revision(conn).await;
|
||||
|
||||
FolderCipher::delete_all_by_cipher(&self.uuid, conn).await?;
|
||||
@@ -480,7 +482,7 @@ impl Cipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
// TODO: Optimize this by executing a DELETE directly on the database, instead of first fetching.
|
||||
for cipher in Self::find_by_org(org_uuid, conn).await {
|
||||
cipher.delete(conn).await?;
|
||||
@@ -488,7 +490,7 @@ impl Cipher {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
for cipher in Self::find_owned_by_user(user_uuid, conn).await {
|
||||
cipher.delete(conn).await?;
|
||||
}
|
||||
@@ -496,7 +498,7 @@ impl Cipher {
|
||||
}
|
||||
|
||||
/// Purge all ciphers that are old enough to be auto-deleted.
|
||||
pub async fn purge_trash(conn: &mut DbConn) {
|
||||
pub async fn purge_trash(conn: &DbConn) {
|
||||
if let Some(auto_delete_days) = CONFIG.trash_auto_delete_days() {
|
||||
let now = Utc::now().naive_utc();
|
||||
let dt = now - TimeDelta::try_days(auto_delete_days).unwrap();
|
||||
@@ -510,7 +512,7 @@ impl Cipher {
|
||||
&self,
|
||||
folder_uuid: Option<FolderId>,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
|
||||
@@ -550,7 +552,7 @@ impl Cipher {
|
||||
&self,
|
||||
user_uuid: &UserId,
|
||||
cipher_sync_data: Option<&CipherSyncData>,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> bool {
|
||||
if let Some(ref org_uuid) = self.organization_uuid {
|
||||
if let Some(cipher_sync_data) = cipher_sync_data {
|
||||
@@ -569,7 +571,7 @@ impl Cipher {
|
||||
&self,
|
||||
user_uuid: &UserId,
|
||||
cipher_sync_data: Option<&CipherSyncData>,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> bool {
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
return false;
|
||||
@@ -593,7 +595,7 @@ impl Cipher {
|
||||
&self,
|
||||
user_uuid: &UserId,
|
||||
cipher_sync_data: Option<&CipherSyncData>,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<(bool, bool, bool)> {
|
||||
// Check whether this cipher is directly owned by the user, or is in
|
||||
// a collection that the user has full access to. If so, there are no
|
||||
@@ -659,12 +661,8 @@ impl Cipher {
|
||||
Some((read_only, hide_passwords, manage))
|
||||
}
|
||||
|
||||
async fn get_user_collections_access_flags(
|
||||
&self,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
) -> Vec<(bool, bool, bool)> {
|
||||
db_run! {conn: {
|
||||
async fn get_user_collections_access_flags(&self, user_uuid: &UserId, conn: &DbConn) -> Vec<(bool, bool, bool)> {
|
||||
db_run! { conn: {
|
||||
// Check whether this cipher is in any collections accessible to the
|
||||
// user. If so, retrieve the access flags for each collection.
|
||||
ciphers::table
|
||||
@@ -680,15 +678,11 @@ impl Cipher {
|
||||
}}
|
||||
}
|
||||
|
||||
async fn get_group_collections_access_flags(
|
||||
&self,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
) -> Vec<(bool, bool, bool)> {
|
||||
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: {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(ciphers::uuid.eq(&self.uuid))
|
||||
.inner_join(ciphers_collections::table.on(
|
||||
@@ -710,7 +704,7 @@ impl Cipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn is_write_accessible_to_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
pub async fn is_write_accessible_to_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
match self.get_access_restrictions(user_uuid, None, conn).await {
|
||||
Some((read_only, _hide_passwords, manage)) => !read_only || manage,
|
||||
None => false,
|
||||
@@ -719,32 +713,32 @@ impl Cipher {
|
||||
|
||||
// used for checking if collection can be edited (only if user has access to a collection they
|
||||
// can write to and also passwords are not hidden to prevent privilege escalation)
|
||||
pub async fn is_in_editable_collection_by_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
pub async fn is_in_editable_collection_by_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
match self.get_access_restrictions(user_uuid, None, conn).await {
|
||||
Some((read_only, hide_passwords, manage)) => (!read_only && !hide_passwords) || manage,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn is_accessible_to_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
pub async fn is_accessible_to_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
self.get_access_restrictions(user_uuid, None, conn).await.is_some()
|
||||
}
|
||||
|
||||
// Returns whether this cipher is a favorite of the specified user.
|
||||
pub async fn is_favorite(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
pub async fn is_favorite(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
Favorite::is_favorite(&self.uuid, user_uuid, conn).await
|
||||
}
|
||||
|
||||
// Sets whether this cipher is a favorite of the specified user.
|
||||
pub async fn set_favorite(&self, favorite: Option<bool>, user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn set_favorite(&self, favorite: Option<bool>, user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
match favorite {
|
||||
None => Ok(()), // No change requested.
|
||||
Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_folder_uuid(&self, user_uuid: &UserId, conn: &mut DbConn) -> Option<FolderId> {
|
||||
db_run! {conn: {
|
||||
pub async fn get_folder_uuid(&self, user_uuid: &UserId, conn: &DbConn) -> Option<FolderId> {
|
||||
db_run! { conn: {
|
||||
folders_ciphers::table
|
||||
.inner_join(folders::table)
|
||||
.filter(folders::user_uuid.eq(&user_uuid))
|
||||
@@ -755,28 +749,26 @@ impl Cipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &CipherId, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_by_uuid(uuid: &CipherId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(ciphers::uuid.eq(uuid))
|
||||
.first::<CipherDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_org(
|
||||
cipher_uuid: &CipherId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(ciphers::uuid.eq(cipher_uuid))
|
||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||
.first::<CipherDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -796,10 +788,10 @@ impl Cipher {
|
||||
user_uuid: &UserId,
|
||||
visible_only: bool,
|
||||
cipher_uuids: &Vec<CipherId>,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
let mut query = ciphers::table
|
||||
.left_join(ciphers_collections::table.on(
|
||||
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
||||
@@ -848,10 +840,11 @@ impl Cipher {
|
||||
query
|
||||
.select(ciphers::all_columns)
|
||||
.distinct()
|
||||
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
} else {
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
let mut query = ciphers::table
|
||||
.left_join(ciphers_collections::table.on(
|
||||
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
||||
@@ -887,46 +880,44 @@ impl Cipher {
|
||||
query
|
||||
.select(ciphers::all_columns)
|
||||
.distinct()
|
||||
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all ciphers visible to the specified user.
|
||||
pub async fn find_by_user_visible(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_user_visible(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
Self::find_by_user(user_uuid, true, &vec![], conn).await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_ciphers(
|
||||
user_uuid: &UserId,
|
||||
cipher_uuids: &Vec<CipherId>,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
Self::find_by_user(user_uuid, true, cipher_uuids, conn).await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_cipher(
|
||||
user_uuid: &UserId,
|
||||
cipher_uuid: &CipherId,
|
||||
conn: &mut DbConn,
|
||||
) -> Option<Self> {
|
||||
pub async fn find_by_user_and_cipher(user_uuid: &UserId, cipher_uuid: &CipherId, conn: &DbConn) -> Option<Self> {
|
||||
Self::find_by_user(user_uuid, true, &vec![cipher_uuid.clone()], conn).await.pop()
|
||||
}
|
||||
|
||||
// Find all ciphers directly owned by the specified user.
|
||||
pub async fn find_owned_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_owned_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(
|
||||
ciphers::user_uuid.eq(user_uuid)
|
||||
.and(ciphers::organization_uuid.is_null())
|
||||
)
|
||||
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_owned_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 {
|
||||
db_run! {conn: {
|
||||
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()
|
||||
@@ -936,16 +927,17 @@ impl Cipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
|
||||
db_run! {conn: {
|
||||
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()
|
||||
@@ -955,27 +947,29 @@ impl Cipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_folder(folder_uuid: &FolderId, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_by_folder(folder_uuid: &FolderId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
folders_ciphers::table.inner_join(ciphers::table)
|
||||
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
||||
.select(ciphers::all_columns)
|
||||
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
}
|
||||
|
||||
/// Find all ciphers that were deleted before the specified datetime.
|
||||
pub async fn find_deleted_before(dt: &NaiveDateTime, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_deleted_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
ciphers::table
|
||||
.filter(ciphers::deleted_at.lt(dt))
|
||||
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading ciphers")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn get_collections(&self, user_uuid: UserId, conn: &mut DbConn) -> Vec<CollectionId> {
|
||||
pub async fn get_collections(&self, user_uuid: UserId, conn: &DbConn) -> Vec<CollectionId> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
ciphers_collections::table
|
||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||
.inner_join(collections::table.on(
|
||||
@@ -1005,10 +999,11 @@ impl Cipher {
|
||||
.and(collections_groups::read_only.eq(false)))
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<CollectionId>(conn).unwrap_or_default()
|
||||
.load::<CollectionId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
} else {
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
ciphers_collections::table
|
||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||
.inner_join(collections::table.on(
|
||||
@@ -1027,14 +1022,15 @@ impl Cipher {
|
||||
.and(users_collections::read_only.eq(false)))
|
||||
)
|
||||
.select(ciphers_collections::collection_uuid)
|
||||
.load::<CollectionId>(conn).unwrap_or_default()
|
||||
.load::<CollectionId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_admin_collections(&self, user_uuid: UserId, conn: &mut DbConn) -> Vec<CollectionId> {
|
||||
pub async fn get_admin_collections(&self, user_uuid: UserId, conn: &DbConn) -> Vec<CollectionId> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
ciphers_collections::table
|
||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||
.inner_join(collections::table.on(
|
||||
@@ -1065,10 +1061,11 @@ impl Cipher {
|
||||
.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()
|
||||
.load::<CollectionId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
} else {
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
ciphers_collections::table
|
||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||
.inner_join(collections::table.on(
|
||||
@@ -1088,7 +1085,8 @@ impl Cipher {
|
||||
.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()
|
||||
.load::<CollectionId>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1097,9 +1095,9 @@ impl Cipher {
|
||||
/// This is used during a full sync so we only need one query for all collections accessible.
|
||||
pub async fn get_collections_with_cipher_by_user(
|
||||
user_uuid: UserId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<(CipherId, CollectionId)> {
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
ciphers_collections::table
|
||||
.inner_join(collections::table.on(
|
||||
collections::uuid.eq(ciphers_collections::collection_uuid)
|
||||
@@ -1132,7 +1130,8 @@ impl Cipher {
|
||||
.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()
|
||||
.load::<(CipherId, CollectionId)>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,39 +5,41 @@ 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;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = collections)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Collection {
|
||||
pub uuid: CollectionId,
|
||||
pub org_uuid: OrganizationId,
|
||||
pub name: String,
|
||||
pub external_id: Option<String>,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = collections)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Collection {
|
||||
pub uuid: CollectionId,
|
||||
pub org_uuid: OrganizationId,
|
||||
pub name: String,
|
||||
pub external_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = users_collections)]
|
||||
#[diesel(primary_key(user_uuid, collection_uuid))]
|
||||
pub struct CollectionUser {
|
||||
pub user_uuid: UserId,
|
||||
pub collection_uuid: CollectionId,
|
||||
pub read_only: bool,
|
||||
pub hide_passwords: bool,
|
||||
pub manage: bool,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = users_collections)]
|
||||
#[diesel(primary_key(user_uuid, collection_uuid))]
|
||||
pub struct CollectionUser {
|
||||
pub user_uuid: UserId,
|
||||
pub collection_uuid: CollectionId,
|
||||
pub read_only: bool,
|
||||
pub hide_passwords: bool,
|
||||
pub manage: bool,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = ciphers_collections)]
|
||||
#[diesel(primary_key(cipher_uuid, collection_uuid))]
|
||||
pub struct CollectionCipher {
|
||||
pub cipher_uuid: CipherId,
|
||||
pub collection_uuid: CollectionId,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = ciphers_collections)]
|
||||
#[diesel(primary_key(cipher_uuid, collection_uuid))]
|
||||
pub struct CollectionCipher {
|
||||
pub cipher_uuid: CipherId,
|
||||
pub collection_uuid: CollectionId,
|
||||
}
|
||||
|
||||
/// Local methods
|
||||
@@ -83,7 +85,7 @@ impl Collection {
|
||||
&self,
|
||||
user_uuid: &UserId,
|
||||
cipher_sync_data: Option<&crate::api::core::CipherSyncData>,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Value {
|
||||
let (read_only, hide_passwords, manage) = if let Some(cipher_sync_data) = cipher_sync_data {
|
||||
match cipher_sync_data.members.get(&self.org_uuid) {
|
||||
@@ -135,7 +137,7 @@ impl Collection {
|
||||
json_object
|
||||
}
|
||||
|
||||
pub async fn can_access_collection(member: &Membership, col_id: &CollectionId, conn: &mut DbConn) -> bool {
|
||||
pub async fn can_access_collection(member: &Membership, col_id: &CollectionId, conn: &DbConn) -> bool {
|
||||
member.has_status(MembershipStatus::Confirmed)
|
||||
&& (member.has_full_access()
|
||||
|| CollectionUser::has_access_to_collection_by_user(col_id, &member.user_uuid, conn).await
|
||||
@@ -152,13 +154,13 @@ use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Collection {
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
self.update_users_revision(conn).await;
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(collections::table)
|
||||
.values(CollectionDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -166,7 +168,7 @@ impl Collection {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(collections::table)
|
||||
.filter(collections::uuid.eq(&self.uuid))
|
||||
.set(CollectionDb::to_db(self))
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving collection")
|
||||
}
|
||||
@@ -174,19 +176,18 @@ impl Collection {
|
||||
}.map_res("Error saving collection")
|
||||
}
|
||||
postgresql {
|
||||
let value = CollectionDb::to_db(self);
|
||||
diesel::insert_into(collections::table)
|
||||
.values(&value)
|
||||
.values(self)
|
||||
.on_conflict(collections::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving collection")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
self.update_users_revision(conn).await;
|
||||
CollectionCipher::delete_all_by_collection(&self.uuid, conn).await?;
|
||||
CollectionUser::delete_all_by_collection(&self.uuid, conn).await?;
|
||||
@@ -199,30 +200,29 @@ impl Collection {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
for collection in Self::find_by_organization(org_uuid, conn).await {
|
||||
collection.delete(conn).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_users_revision(&self, conn: &mut DbConn) {
|
||||
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() {
|
||||
User::update_uuid_revision(&member.user_uuid, conn).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &CollectionId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_uuid(uuid: &CollectionId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
.filter(collections::uuid.eq(uuid))
|
||||
.first::<CollectionDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user_uuid(user_uuid: UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_user_uuid(user_uuid: UserId, conn: &DbConn) -> Vec<Self> {
|
||||
if CONFIG.org_groups_enabled() {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
@@ -263,7 +263,8 @@ impl Collection {
|
||||
)
|
||||
.select(collections::all_columns)
|
||||
.distinct()
|
||||
.load::<CollectionDb>(conn).expect("Error loading collections").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collections")
|
||||
}}
|
||||
} else {
|
||||
db_run! { conn: {
|
||||
@@ -288,7 +289,8 @@ impl Collection {
|
||||
)
|
||||
.select(collections::all_columns)
|
||||
.distinct()
|
||||
.load::<CollectionDb>(conn).expect("Error loading collections").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collections")
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -296,7 +298,7 @@ impl Collection {
|
||||
pub async fn find_by_organization_and_user_uuid(
|
||||
org_uuid: &OrganizationId,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
Self::find_by_user_uuid(user_uuid.to_owned(), conn)
|
||||
.await
|
||||
@@ -305,17 +307,16 @@ impl Collection {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
.load::<CollectionDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collections")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
@@ -326,23 +327,18 @@ impl Collection {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_org(
|
||||
uuid: &CollectionId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
) -> Option<Self> {
|
||||
pub async fn find_by_uuid_and_org(uuid: &CollectionId, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
.filter(collections::uuid.eq(uuid))
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
.select(collections::all_columns)
|
||||
.first::<CollectionDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &CollectionId, user_uuid: UserId, conn: &mut DbConn) -> Option<Self> {
|
||||
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: {
|
||||
collections::table
|
||||
@@ -380,8 +376,8 @@ impl Collection {
|
||||
)
|
||||
)
|
||||
).select(collections::all_columns)
|
||||
.first::<CollectionDb>(conn).ok()
|
||||
.from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
} else {
|
||||
db_run! { conn: {
|
||||
@@ -403,13 +399,13 @@ impl Collection {
|
||||
users_organizations::atype.le(MembershipType::Admin as i32) // Org admin or owner
|
||||
))
|
||||
).select(collections::all_columns)
|
||||
.first::<CollectionDb>(conn).ok()
|
||||
.from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn is_writable_by_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
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: {
|
||||
@@ -471,7 +467,7 @@ impl Collection {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn hide_passwords_for_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
pub async fn hide_passwords_for_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
let user_uuid = user_uuid.to_string();
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
@@ -517,7 +513,7 @@ impl Collection {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn is_manageable_by_user(&self, user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
pub async fn is_manageable_by_user(&self, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
let user_uuid = user_uuid.to_string();
|
||||
db_run! { conn: {
|
||||
collections::table
|
||||
@@ -569,7 +565,7 @@ impl CollectionUser {
|
||||
pub async fn find_by_organization_and_user_uuid(
|
||||
org_uuid: &OrganizationId,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_collections::table
|
||||
@@ -577,15 +573,14 @@ impl CollectionUser {
|
||||
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
|
||||
.filter(collections::org_uuid.eq(org_uuid))
|
||||
.select(users_collections::all_columns)
|
||||
.load::<CollectionUserDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_organization_swap_user_uuid_with_member_uuid(
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<CollectionMembership> {
|
||||
let col_users = db_run! { conn: {
|
||||
users_collections::table
|
||||
@@ -594,9 +589,8 @@ impl CollectionUser {
|
||||
.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::<CollectionUserDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
.from_db()
|
||||
}};
|
||||
col_users.into_iter().map(|c| c.into()).collect()
|
||||
}
|
||||
@@ -607,7 +601,7 @@ impl CollectionUser {
|
||||
read_only: bool,
|
||||
hide_passwords: bool,
|
||||
manage: bool,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
User::update_uuid_revision(user_uuid, conn).await;
|
||||
|
||||
@@ -664,7 +658,7 @@ impl CollectionUser {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(&self.user_uuid, conn).await;
|
||||
|
||||
db_run! { conn: {
|
||||
@@ -678,21 +672,20 @@ impl CollectionUser {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_collections::table
|
||||
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
||||
.select(users_collections::all_columns)
|
||||
.load::<CollectionUserDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_org_and_coll_swap_user_uuid_with_member_uuid(
|
||||
org_uuid: &OrganizationId,
|
||||
collection_uuid: &CollectionId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<CollectionMembership> {
|
||||
let col_users = db_run! { conn: {
|
||||
users_collections::table
|
||||
@@ -700,9 +693,8 @@ impl CollectionUser {
|
||||
.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::<CollectionUserDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
.from_db()
|
||||
}};
|
||||
col_users.into_iter().map(|c| c.into()).collect()
|
||||
}
|
||||
@@ -710,31 +702,29 @@ impl CollectionUser {
|
||||
pub async fn find_by_collection_and_user(
|
||||
collection_uuid: &CollectionId,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { 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::<CollectionUserDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_collections::table
|
||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
||||
.select(users_collections::all_columns)
|
||||
.load::<CollectionUserDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading users_collections")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
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() {
|
||||
User::update_uuid_revision(&collection.user_uuid, conn).await;
|
||||
}
|
||||
@@ -749,7 +739,7 @@ impl CollectionUser {
|
||||
pub async fn delete_all_by_user_and_org(
|
||||
user_uuid: &UserId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
let collectionusers = Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn).await;
|
||||
|
||||
@@ -766,18 +756,14 @@ impl CollectionUser {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn has_access_to_collection_by_user(
|
||||
col_id: &CollectionId,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
) -> bool {
|
||||
pub async fn has_access_to_collection_by_user(col_id: &CollectionId, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
Self::find_by_collection_and_user(col_id, user_uuid, conn).await.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Database methods
|
||||
impl CollectionCipher {
|
||||
pub async fn save(cipher_uuid: &CipherId, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(cipher_uuid: &CipherId, collection_uuid: &CollectionId, conn: &DbConn) -> EmptyResult {
|
||||
Self::update_users_revision(collection_uuid, conn).await;
|
||||
|
||||
db_run! { conn:
|
||||
@@ -807,7 +793,7 @@ impl CollectionCipher {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(cipher_uuid: &CipherId, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(cipher_uuid: &CipherId, collection_uuid: &CollectionId, conn: &DbConn) -> EmptyResult {
|
||||
Self::update_users_revision(collection_uuid, conn).await;
|
||||
|
||||
db_run! { conn: {
|
||||
@@ -821,7 +807,7 @@ impl CollectionCipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)))
|
||||
.execute(conn)
|
||||
@@ -829,7 +815,7 @@ impl CollectionCipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid)))
|
||||
.execute(conn)
|
||||
@@ -837,7 +823,7 @@ impl CollectionCipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn update_users_revision(collection_uuid: &CollectionId, conn: &mut DbConn) {
|
||||
pub async fn update_users_revision(collection_uuid: &CollectionId, conn: &DbConn) {
|
||||
if let Some(collection) = Collection::find_by_uuid(collection_uuid, conn).await {
|
||||
collection.update_users_revision(conn).await;
|
||||
}
|
||||
|
||||
@@ -5,32 +5,32 @@ use derive_more::{Display, From};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{AuthRequest, UserId};
|
||||
use crate::db::schema::devices;
|
||||
use crate::{
|
||||
crypto,
|
||||
util::{format_date, get_uuid},
|
||||
};
|
||||
use diesel::prelude::*;
|
||||
use macros::{IdFromParam, UuidFromParam};
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = devices)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid, user_uuid))]
|
||||
pub struct Device {
|
||||
pub uuid: DeviceId,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = devices)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid, user_uuid))]
|
||||
pub struct Device {
|
||||
pub uuid: DeviceId,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
|
||||
pub user_uuid: UserId,
|
||||
pub user_uuid: UserId,
|
||||
|
||||
pub name: String,
|
||||
pub atype: i32, // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/Enums/DeviceType.cs
|
||||
pub push_uuid: Option<PushId>,
|
||||
pub push_token: Option<String>,
|
||||
pub name: String,
|
||||
pub atype: i32, // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/Enums/DeviceType.cs
|
||||
pub push_uuid: Option<PushId>,
|
||||
pub push_token: Option<String>,
|
||||
|
||||
pub refresh_token: String,
|
||||
pub twofactor_remember: Option<String>,
|
||||
}
|
||||
pub refresh_token: String,
|
||||
pub twofactor_remember: Option<String>,
|
||||
}
|
||||
|
||||
/// Local methods
|
||||
@@ -115,13 +115,7 @@ use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Device {
|
||||
pub async fn new(
|
||||
uuid: DeviceId,
|
||||
user_uuid: UserId,
|
||||
name: String,
|
||||
atype: i32,
|
||||
conn: &mut DbConn,
|
||||
) -> ApiResult<Device> {
|
||||
pub async fn new(uuid: DeviceId, user_uuid: UserId, name: String, atype: i32, conn: &DbConn) -> ApiResult<Device> {
|
||||
let now = Utc::now().naive_utc();
|
||||
|
||||
let device = Self {
|
||||
@@ -142,18 +136,24 @@ impl Device {
|
||||
device.inner_save(conn).await.map(|()| device)
|
||||
}
|
||||
|
||||
async fn inner_save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
async fn inner_save(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
crate::util::retry(
|
||||
|| diesel::replace_into(devices::table).values(DeviceDb::to_db(self)).execute(conn),
|
||||
crate::util::retry(||
|
||||
diesel::replace_into(devices::table)
|
||||
.values(self)
|
||||
.execute(conn),
|
||||
10,
|
||||
).map_res("Error saving device")
|
||||
}
|
||||
postgresql {
|
||||
let value = DeviceDb::to_db(self);
|
||||
crate::util::retry(
|
||||
|| diesel::insert_into(devices::table).values(&value).on_conflict((devices::uuid, devices::user_uuid)).do_update().set(&value).execute(conn),
|
||||
crate::util::retry(||
|
||||
diesel::insert_into(devices::table)
|
||||
.values(self)
|
||||
.on_conflict((devices::uuid, devices::user_uuid))
|
||||
.do_update()
|
||||
.set(self)
|
||||
.execute(conn),
|
||||
10,
|
||||
).map_res("Error saving device")
|
||||
}
|
||||
@@ -161,12 +161,12 @@ impl Device {
|
||||
}
|
||||
|
||||
// Should only be called after user has passed authentication
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
self.updated_at = Utc::now().naive_utc();
|
||||
self.inner_save(conn).await
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(devices::table.filter(devices::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
@@ -174,18 +174,17 @@ impl Device {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &DeviceId, user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_uuid_and_user(uuid: &DeviceId, user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
devices::table
|
||||
.filter(devices::uuid.eq(uuid))
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
.first::<DeviceDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_with_auth_request_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<DeviceWithAuthRequest> {
|
||||
pub async fn find_with_auth_request_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<DeviceWithAuthRequest> {
|
||||
let devices = Self::find_by_user(user_uuid, conn).await;
|
||||
let mut result = Vec::new();
|
||||
for device in devices {
|
||||
@@ -195,27 +194,25 @@ impl Device {
|
||||
result
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
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::<DeviceDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading devices")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &DeviceId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_uuid(uuid: &DeviceId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
devices::table
|
||||
.filter(devices::uuid.eq(uuid))
|
||||
.first::<DeviceDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn clear_push_token_by_uuid(uuid: &DeviceId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn clear_push_token_by_uuid(uuid: &DeviceId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::update(devices::table)
|
||||
.filter(devices::uuid.eq(uuid))
|
||||
@@ -224,39 +221,36 @@ impl Device {
|
||||
.map_res("Error removing push token")
|
||||
}}
|
||||
}
|
||||
pub async fn find_by_refresh_token(refresh_token: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
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::<DeviceDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_latest_active_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_latest_active_by_user(user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
devices::table
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
.order(devices::updated_at.desc())
|
||||
.first::<DeviceDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_push_devices_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_push_devices_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
devices::table
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
.filter(devices::push_token.is_not_null())
|
||||
.load::<DeviceDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading push devices")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn check_user_has_push_device(user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
pub async fn check_user_has_push_device(user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
db_run! { conn: {
|
||||
devices::table
|
||||
.filter(devices::user_uuid.eq(user_uuid))
|
||||
|
||||
@@ -3,32 +3,31 @@ use derive_more::{AsRef, Deref, Display, From};
|
||||
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 macros::UuidFromParam;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = emergency_access)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct EmergencyAccess {
|
||||
pub uuid: EmergencyAccessId,
|
||||
pub grantor_uuid: UserId,
|
||||
pub grantee_uuid: Option<UserId>,
|
||||
pub email: Option<String>,
|
||||
pub key_encrypted: Option<String>,
|
||||
pub atype: i32, //EmergencyAccessType
|
||||
pub status: i32, //EmergencyAccessStatus
|
||||
pub wait_time_days: i32,
|
||||
pub recovery_initiated_at: Option<NaiveDateTime>,
|
||||
pub last_notification_at: Option<NaiveDateTime>,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub created_at: NaiveDateTime,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = emergency_access)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct EmergencyAccess {
|
||||
pub uuid: EmergencyAccessId,
|
||||
pub grantor_uuid: UserId,
|
||||
pub grantee_uuid: Option<UserId>,
|
||||
pub email: Option<String>,
|
||||
pub key_encrypted: Option<String>,
|
||||
pub atype: i32, //EmergencyAccessType
|
||||
pub status: i32, //EmergencyAccessStatus
|
||||
pub wait_time_days: i32,
|
||||
pub recovery_initiated_at: Option<NaiveDateTime>,
|
||||
pub last_notification_at: Option<NaiveDateTime>,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub created_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
// Local methods
|
||||
|
||||
impl EmergencyAccess {
|
||||
pub fn new(grantor_uuid: UserId, email: String, status: i32, atype: i32, wait_time_days: i32) -> Self {
|
||||
let now = Utc::now().naive_utc();
|
||||
@@ -67,7 +66,7 @@ impl EmergencyAccess {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn to_json_grantor_details(&self, conn: &mut DbConn) -> Value {
|
||||
pub async fn to_json_grantor_details(&self, conn: &DbConn) -> Value {
|
||||
let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).await.expect("Grantor user not found.");
|
||||
|
||||
json!({
|
||||
@@ -83,7 +82,7 @@ impl EmergencyAccess {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn to_json_grantee_details(&self, conn: &mut DbConn) -> Option<Value> {
|
||||
pub async fn to_json_grantee_details(&self, conn: &DbConn) -> Option<Value> {
|
||||
let grantee_user = if let Some(grantee_uuid) = &self.grantee_uuid {
|
||||
User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")
|
||||
} else if let Some(email) = self.email.as_deref() {
|
||||
@@ -140,14 +139,14 @@ pub enum EmergencyAccessStatus {
|
||||
// region Database methods
|
||||
|
||||
impl EmergencyAccess {
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(&self.grantor_uuid, conn).await;
|
||||
self.updated_at = Utc::now().naive_utc();
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(emergency_access::table)
|
||||
.values(EmergencyAccessDb::to_db(self))
|
||||
.values(&*self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -155,7 +154,7 @@ impl EmergencyAccess {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(emergency_access::table)
|
||||
.filter(emergency_access::uuid.eq(&self.uuid))
|
||||
.set(EmergencyAccessDb::to_db(self))
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error updating emergency access")
|
||||
}
|
||||
@@ -163,12 +162,11 @@ impl EmergencyAccess {
|
||||
}.map_res("Error saving emergency access")
|
||||
}
|
||||
postgresql {
|
||||
let value = EmergencyAccessDb::to_db(self);
|
||||
diesel::insert_into(emergency_access::table)
|
||||
.values(&value)
|
||||
.values(&*self)
|
||||
.on_conflict(emergency_access::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving emergency access")
|
||||
}
|
||||
@@ -179,14 +177,14 @@ impl EmergencyAccess {
|
||||
&mut self,
|
||||
status: i32,
|
||||
date: &NaiveDateTime,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
// Update the grantee so that it will refresh it's status.
|
||||
User::update_uuid_revision(self.grantee_uuid.as_ref().expect("Error getting grantee"), conn).await;
|
||||
self.status = status;
|
||||
date.clone_into(&mut self.updated_at);
|
||||
|
||||
db_run! {conn: {
|
||||
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)))
|
||||
@@ -196,15 +194,11 @@ impl EmergencyAccess {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn update_last_notification_date_and_save(
|
||||
&mut self,
|
||||
date: &NaiveDateTime,
|
||||
conn: &mut DbConn,
|
||||
) -> EmptyResult {
|
||||
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: {
|
||||
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)))
|
||||
@@ -214,7 +208,7 @@ impl EmergencyAccess {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await {
|
||||
ea.delete(conn).await?;
|
||||
}
|
||||
@@ -224,14 +218,14 @@ impl EmergencyAccess {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_grantee_email(grantee_email: &str, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_grantee_email(grantee_email: &str, conn: &DbConn) -> EmptyResult {
|
||||
for ea in Self::find_all_invited_by_grantee_email(grantee_email, conn).await {
|
||||
ea.delete(conn).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(&self.grantor_uuid, conn).await;
|
||||
|
||||
db_run! { conn: {
|
||||
@@ -245,109 +239,108 @@ impl EmergencyAccess {
|
||||
grantor_uuid: &UserId,
|
||||
grantee_uuid: &UserId,
|
||||
email: &str,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { 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::<EmergencyAccessDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_all_recoveries_initiated(conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_all_recoveries_initiated(conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32))
|
||||
.filter(emergency_access::recovery_initiated_at.is_not_null())
|
||||
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading emergency_access")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_grantor_uuid(
|
||||
uuid: &EmergencyAccessId,
|
||||
grantor_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::uuid.eq(uuid))
|
||||
.filter(emergency_access::grantor_uuid.eq(grantor_uuid))
|
||||
.first::<EmergencyAccessDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_grantee_uuid(
|
||||
uuid: &EmergencyAccessId,
|
||||
grantee_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::uuid.eq(uuid))
|
||||
.filter(emergency_access::grantee_uuid.eq(grantee_uuid))
|
||||
.first::<EmergencyAccessDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_grantee_email(
|
||||
uuid: &EmergencyAccessId,
|
||||
grantee_email: &str,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::uuid.eq(uuid))
|
||||
.filter(emergency_access::email.eq(grantee_email))
|
||||
.first::<EmergencyAccessDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_all_by_grantee_uuid(grantee_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_all_by_grantee_uuid(grantee_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::grantee_uuid.eq(grantee_uuid))
|
||||
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading emergency_access")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_invited_by_grantee_email(grantee_email: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_invited_by_grantee_email(grantee_email: &str, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::email.eq(grantee_email))
|
||||
.filter(emergency_access::status.eq(EmergencyAccessStatus::Invited as i32))
|
||||
.first::<EmergencyAccessDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_all_invited_by_grantee_email(grantee_email: &str, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_all_invited_by_grantee_email(grantee_email: &str, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::email.eq(grantee_email))
|
||||
.filter(emergency_access::status.eq(EmergencyAccessStatus::Invited as i32))
|
||||
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading emergency_access")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_all_by_grantor_uuid(grantor_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_all_by_grantor_uuid(grantor_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
emergency_access::table
|
||||
.filter(emergency_access::grantor_uuid.eq(grantor_uuid))
|
||||
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading emergency_access")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn accept_invite(
|
||||
&mut self,
|
||||
grantee_uuid: &UserId,
|
||||
grantee_email: &str,
|
||||
conn: &mut DbConn,
|
||||
) -> EmptyResult {
|
||||
pub async fn accept_invite(&mut self, grantee_uuid: &UserId, grantee_email: &str, conn: &DbConn) -> EmptyResult {
|
||||
if self.email.is_none() || self.email.as_ref().unwrap() != grantee_email {
|
||||
err!("User email does not match invite.");
|
||||
}
|
||||
|
||||
@@ -3,37 +3,37 @@ use chrono::{NaiveDateTime, TimeDelta, Utc};
|
||||
use serde_json::Value;
|
||||
|
||||
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/
|
||||
|
||||
db_object! {
|
||||
// Upstream: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Services/Implementations/EventService.cs
|
||||
// Upstream: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Public/Models/Response/EventResponseModel.cs
|
||||
// Upstream SQL: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Sql/dbo/Tables/Event.sql
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = event)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Event {
|
||||
pub uuid: EventId,
|
||||
pub event_type: i32, // EventType
|
||||
pub user_uuid: Option<UserId>,
|
||||
pub org_uuid: Option<OrganizationId>,
|
||||
pub cipher_uuid: Option<CipherId>,
|
||||
pub collection_uuid: Option<CollectionId>,
|
||||
pub group_uuid: Option<GroupId>,
|
||||
pub org_user_uuid: Option<MembershipId>,
|
||||
pub act_user_uuid: Option<UserId>,
|
||||
// Upstream enum: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/Enums/DeviceType.cs
|
||||
pub device_type: Option<i32>,
|
||||
pub ip_address: Option<String>,
|
||||
pub event_date: NaiveDateTime,
|
||||
pub policy_uuid: Option<OrgPolicyId>,
|
||||
pub provider_uuid: Option<String>,
|
||||
pub provider_user_uuid: Option<String>,
|
||||
pub provider_org_uuid: Option<String>,
|
||||
}
|
||||
// Upstream: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Services/Implementations/EventService.cs
|
||||
// Upstream: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Public/Models/Response/EventResponseModel.cs
|
||||
// Upstream SQL: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Sql/dbo/Tables/Event.sql
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = event)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Event {
|
||||
pub uuid: EventId,
|
||||
pub event_type: i32, // EventType
|
||||
pub user_uuid: Option<UserId>,
|
||||
pub org_uuid: Option<OrganizationId>,
|
||||
pub cipher_uuid: Option<CipherId>,
|
||||
pub collection_uuid: Option<CollectionId>,
|
||||
pub group_uuid: Option<GroupId>,
|
||||
pub org_user_uuid: Option<MembershipId>,
|
||||
pub act_user_uuid: Option<UserId>,
|
||||
// Upstream enum: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/Enums/DeviceType.cs
|
||||
pub device_type: Option<i32>,
|
||||
pub ip_address: Option<String>,
|
||||
pub event_date: NaiveDateTime,
|
||||
pub policy_uuid: Option<OrgPolicyId>,
|
||||
pub provider_uuid: Option<String>,
|
||||
pub provider_user_uuid: Option<String>,
|
||||
pub provider_org_uuid: Option<String>,
|
||||
}
|
||||
|
||||
// Upstream enum: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Enums/EventType.cs
|
||||
@@ -193,27 +193,27 @@ impl Event {
|
||||
|
||||
/// #############
|
||||
/// Basic Queries
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
diesel::replace_into(event::table)
|
||||
.values(EventDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving event")
|
||||
}
|
||||
postgresql {
|
||||
diesel::insert_into(event::table)
|
||||
.values(EventDb::to_db(self))
|
||||
.values(self)
|
||||
.on_conflict(event::uuid)
|
||||
.do_update()
|
||||
.set(EventDb::to_db(self))
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving event")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save_user_event(events: Vec<Event>, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save_user_event(events: Vec<Event>, conn: &DbConn) -> EmptyResult {
|
||||
// Special save function which is able to handle multiple events.
|
||||
// SQLite doesn't support the DEFAULT argument, and does not support inserting multiple values at the same time.
|
||||
// MySQL and PostgreSQL do.
|
||||
@@ -224,14 +224,13 @@ impl Event {
|
||||
sqlite {
|
||||
for event in events {
|
||||
diesel::insert_or_ignore_into(event::table)
|
||||
.values(EventDb::to_db(&event))
|
||||
.values(&event)
|
||||
.execute(conn)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
mysql {
|
||||
let events: Vec<EventDb> = events.iter().map(EventDb::to_db).collect();
|
||||
diesel::insert_or_ignore_into(event::table)
|
||||
.values(&events)
|
||||
.execute(conn)
|
||||
@@ -239,7 +238,6 @@ impl Event {
|
||||
Ok(())
|
||||
}
|
||||
postgresql {
|
||||
let events: Vec<EventDb> = events.iter().map(EventDb::to_db).collect();
|
||||
diesel::insert_into(event::table)
|
||||
.values(&events)
|
||||
.on_conflict_do_nothing()
|
||||
@@ -250,7 +248,7 @@ impl Event {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(event::table.filter(event::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
@@ -264,7 +262,7 @@ impl Event {
|
||||
org_uuid: &OrganizationId,
|
||||
start: &NaiveDateTime,
|
||||
end: &NaiveDateTime,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
event::table
|
||||
@@ -272,13 +270,12 @@ impl Event {
|
||||
.filter(event::event_date.between(start, end))
|
||||
.order_by(event::event_date.desc())
|
||||
.limit(Self::PAGE_SIZE)
|
||||
.load::<EventDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error filtering events")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
event::table
|
||||
.filter(event::org_uuid.eq(org_uuid))
|
||||
@@ -294,7 +291,7 @@ impl Event {
|
||||
member_uuid: &MembershipId,
|
||||
start: &NaiveDateTime,
|
||||
end: &NaiveDateTime,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
event::table
|
||||
@@ -305,9 +302,8 @@ impl Event {
|
||||
.select(event::all_columns)
|
||||
.order_by(event::event_date.desc())
|
||||
.limit(Self::PAGE_SIZE)
|
||||
.load::<EventDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error filtering events")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -315,7 +311,7 @@ impl Event {
|
||||
cipher_uuid: &CipherId,
|
||||
start: &NaiveDateTime,
|
||||
end: &NaiveDateTime,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
event::table
|
||||
@@ -323,13 +319,12 @@ impl Event {
|
||||
.filter(event::event_date.between(start, end))
|
||||
.order_by(event::event_date.desc())
|
||||
.limit(Self::PAGE_SIZE)
|
||||
.load::<EventDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error filtering events")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn clean_events(conn: &mut DbConn) -> EmptyResult {
|
||||
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: {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use super::{CipherId, User, UserId};
|
||||
use crate::db::schema::favorites;
|
||||
use diesel::prelude::*;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = favorites)]
|
||||
#[diesel(primary_key(user_uuid, cipher_uuid))]
|
||||
pub struct Favorite {
|
||||
pub user_uuid: UserId,
|
||||
pub cipher_uuid: CipherId,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = favorites)]
|
||||
#[diesel(primary_key(user_uuid, cipher_uuid))]
|
||||
pub struct Favorite {
|
||||
pub user_uuid: UserId,
|
||||
pub cipher_uuid: CipherId,
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
@@ -17,14 +17,16 @@ 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: &mut DbConn) -> bool {
|
||||
pub async fn is_favorite(cipher_uuid: &CipherId, user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
db_run! { 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
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -33,7 +35,7 @@ impl Favorite {
|
||||
favorite: bool,
|
||||
cipher_uuid: &CipherId,
|
||||
user_uuid: &UserId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn).await, favorite);
|
||||
match (old, new) {
|
||||
@@ -67,7 +69,7 @@ impl Favorite {
|
||||
}
|
||||
|
||||
// Delete all favorite entries associated with the specified cipher.
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid)))
|
||||
.execute(conn)
|
||||
@@ -76,7 +78,7 @@ impl Favorite {
|
||||
}
|
||||
|
||||
// Delete all favorite entries associated with the specified user.
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(favorites::table.filter(favorites::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
@@ -86,7 +88,7 @@ impl Favorite {
|
||||
|
||||
/// 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: &mut DbConn) -> Vec<CipherId> {
|
||||
pub async fn get_all_cipher_uuid_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<CipherId> {
|
||||
db_run! { conn: {
|
||||
favorites::table
|
||||
.filter(favorites::user_uuid.eq(user_uuid))
|
||||
|
||||
@@ -3,27 +3,27 @@ use derive_more::{AsRef, Deref, Display, From};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{CipherId, User, UserId};
|
||||
use crate::db::schema::{folders, folders_ciphers};
|
||||
use diesel::prelude::*;
|
||||
use macros::UuidFromParam;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = folders)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Folder {
|
||||
pub uuid: FolderId,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub user_uuid: UserId,
|
||||
pub name: String,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = folders)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Folder {
|
||||
pub uuid: FolderId,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub user_uuid: UserId,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = folders_ciphers)]
|
||||
#[diesel(primary_key(cipher_uuid, folder_uuid))]
|
||||
pub struct FolderCipher {
|
||||
pub cipher_uuid: CipherId,
|
||||
pub folder_uuid: FolderId,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = folders_ciphers)]
|
||||
#[diesel(primary_key(cipher_uuid, folder_uuid))]
|
||||
pub struct FolderCipher {
|
||||
pub cipher_uuid: CipherId,
|
||||
pub folder_uuid: FolderId,
|
||||
}
|
||||
|
||||
/// Local methods
|
||||
@@ -69,14 +69,14 @@ use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Folder {
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(&self.user_uuid, conn).await;
|
||||
self.updated_at = Utc::now().naive_utc();
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(folders::table)
|
||||
.values(FolderDb::to_db(self))
|
||||
.values(&*self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -84,7 +84,7 @@ impl Folder {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(folders::table)
|
||||
.filter(folders::uuid.eq(&self.uuid))
|
||||
.set(FolderDb::to_db(self))
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving folder")
|
||||
}
|
||||
@@ -92,19 +92,18 @@ impl Folder {
|
||||
}.map_res("Error saving folder")
|
||||
}
|
||||
postgresql {
|
||||
let value = FolderDb::to_db(self);
|
||||
diesel::insert_into(folders::table)
|
||||
.values(&value)
|
||||
.values(&*self)
|
||||
.on_conflict(folders::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving folder")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(&self.user_uuid, conn).await;
|
||||
FolderCipher::delete_all_by_folder(&self.uuid, conn).await?;
|
||||
|
||||
@@ -115,50 +114,48 @@ impl Folder {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
for folder in Self::find_by_user(user_uuid, conn).await {
|
||||
folder.delete(conn).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &FolderId, user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_uuid_and_user(uuid: &FolderId, user_uuid: &UserId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
folders::table
|
||||
.filter(folders::uuid.eq(uuid))
|
||||
.filter(folders::user_uuid.eq(user_uuid))
|
||||
.first::<FolderDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
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::<FolderDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading folders")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
impl FolderCipher {
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
// Not checking for ForeignKey Constraints here.
|
||||
// Table folders_ciphers does not have ForeignKey Constraints which would cause conflicts.
|
||||
// This table has no constraints pointing to itself, but only to others.
|
||||
diesel::replace_into(folders_ciphers::table)
|
||||
.values(FolderCipherDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
.map_res("Error adding cipher to folder")
|
||||
}
|
||||
postgresql {
|
||||
diesel::insert_into(folders_ciphers::table)
|
||||
.values(FolderCipherDb::to_db(self))
|
||||
.values(self)
|
||||
.on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid))
|
||||
.do_nothing()
|
||||
.execute(conn)
|
||||
@@ -167,7 +164,7 @@ impl FolderCipher {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(
|
||||
folders_ciphers::table
|
||||
@@ -179,7 +176,7 @@ impl FolderCipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)))
|
||||
.execute(conn)
|
||||
@@ -187,7 +184,7 @@ impl FolderCipher {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_folder(folder_uuid: &FolderId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_folder(folder_uuid: &FolderId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid)))
|
||||
.execute(conn)
|
||||
@@ -198,31 +195,29 @@ impl FolderCipher {
|
||||
pub async fn find_by_folder_and_cipher(
|
||||
folder_uuid: &FolderId,
|
||||
cipher_uuid: &CipherId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
folders_ciphers::table
|
||||
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
||||
.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))
|
||||
.first::<FolderCipherDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_folder(folder_uuid: &FolderId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_folder(folder_uuid: &FolderId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
folders_ciphers::table
|
||||
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
||||
.load::<FolderCipherDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading folders")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
/// 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: &mut DbConn) -> Vec<(CipherId, FolderId)> {
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<(CipherId, FolderId)> {
|
||||
db_run! { conn: {
|
||||
folders_ciphers::table
|
||||
.inner_join(folders::table)
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
use super::{CollectionId, Membership, MembershipId, OrganizationId, User, UserId};
|
||||
use crate::api::EmptyResult;
|
||||
use crate::db::schema::{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;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = groups)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Group {
|
||||
pub uuid: GroupId,
|
||||
pub organizations_uuid: OrganizationId,
|
||||
pub name: String,
|
||||
pub access_all: bool,
|
||||
pub external_id: Option<String>,
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub revision_date: NaiveDateTime,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = groups)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Group {
|
||||
pub uuid: GroupId,
|
||||
pub organizations_uuid: OrganizationId,
|
||||
pub name: String,
|
||||
pub access_all: bool,
|
||||
pub external_id: Option<String>,
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub revision_date: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = collections_groups)]
|
||||
#[diesel(primary_key(collections_uuid, groups_uuid))]
|
||||
pub struct CollectionGroup {
|
||||
pub collections_uuid: CollectionId,
|
||||
pub groups_uuid: GroupId,
|
||||
pub read_only: bool,
|
||||
pub hide_passwords: bool,
|
||||
pub manage: bool,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = collections_groups)]
|
||||
#[diesel(primary_key(collections_uuid, groups_uuid))]
|
||||
pub struct CollectionGroup {
|
||||
pub collections_uuid: CollectionId,
|
||||
pub groups_uuid: GroupId,
|
||||
pub read_only: bool,
|
||||
pub hide_passwords: bool,
|
||||
pub manage: bool,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = groups_users)]
|
||||
#[diesel(primary_key(groups_uuid, users_organizations_uuid))]
|
||||
pub struct GroupUser {
|
||||
pub groups_uuid: GroupId,
|
||||
pub users_organizations_uuid: MembershipId
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = groups_users)]
|
||||
#[diesel(primary_key(groups_uuid, users_organizations_uuid))]
|
||||
pub struct GroupUser {
|
||||
pub groups_uuid: GroupId,
|
||||
pub users_organizations_uuid: MembershipId,
|
||||
}
|
||||
|
||||
/// Local methods
|
||||
@@ -77,7 +77,7 @@ impl Group {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn to_json_details(&self, conn: &mut DbConn) -> Value {
|
||||
pub async fn to_json_details(&self, conn: &DbConn) -> Value {
|
||||
// If both read_only and hide_passwords are false, then manage should be true
|
||||
// You can't have an entry with read_only and manage, or hide_passwords and manage
|
||||
// Or an entry with everything to false
|
||||
@@ -156,13 +156,13 @@ impl GroupUser {
|
||||
|
||||
/// Database methods
|
||||
impl Group {
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
self.revision_date = Utc::now().naive_utc();
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(groups::table)
|
||||
.values(GroupDb::to_db(self))
|
||||
.values(&*self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -170,7 +170,7 @@ impl Group {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(groups::table)
|
||||
.filter(groups::uuid.eq(&self.uuid))
|
||||
.set(GroupDb::to_db(self))
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving group")
|
||||
}
|
||||
@@ -178,36 +178,34 @@ impl Group {
|
||||
}.map_res("Error saving group")
|
||||
}
|
||||
postgresql {
|
||||
let value = GroupDb::to_db(self);
|
||||
diesel::insert_into(groups::table)
|
||||
.values(&value)
|
||||
.values(&*self)
|
||||
.on_conflict(groups::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving group")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
for group in Self::find_by_organization(org_uuid, conn).await {
|
||||
group.delete(conn).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
groups::table
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
.load::<GroupDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading groups")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
groups::table
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
@@ -218,33 +216,31 @@ impl Group {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_org(uuid: &GroupId, org_uuid: &OrganizationId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_uuid_and_org(uuid: &GroupId, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
groups::table
|
||||
.filter(groups::uuid.eq(uuid))
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
.first::<GroupDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_external_id_and_org(
|
||||
external_id: &str,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
groups::table
|
||||
.filter(groups::external_id.eq(external_id))
|
||||
.filter(groups::organizations_uuid.eq(org_uuid))
|
||||
.first::<GroupDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
//Returns all organizations the user has full access to
|
||||
pub async fn get_orgs_by_user_with_full_access(user_uuid: &UserId, conn: &mut DbConn) -> Vec<OrganizationId> {
|
||||
pub async fn get_orgs_by_user_with_full_access(user_uuid: &UserId, conn: &DbConn) -> Vec<OrganizationId> {
|
||||
db_run! { conn: {
|
||||
groups_users::table
|
||||
.inner_join(users_organizations::table.on(
|
||||
@@ -262,7 +258,7 @@ impl Group {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn is_in_full_access_group(user_uuid: &UserId, org_uuid: &OrganizationId, conn: &mut DbConn) -> bool {
|
||||
pub async fn is_in_full_access_group(user_uuid: &UserId, org_uuid: &OrganizationId, conn: &DbConn) -> bool {
|
||||
db_run! { conn: {
|
||||
groups::table
|
||||
.inner_join(groups_users::table.on(
|
||||
@@ -280,7 +276,7 @@ impl Group {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
CollectionGroup::delete_all_by_group(&self.uuid, conn).await?;
|
||||
GroupUser::delete_all_by_group(&self.uuid, conn).await?;
|
||||
|
||||
@@ -291,14 +287,14 @@ impl Group {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn update_revision(uuid: &GroupId, conn: &mut DbConn) {
|
||||
pub async fn update_revision(uuid: &GroupId, conn: &DbConn) {
|
||||
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
|
||||
warn!("Failed to update revision for {uuid}: {e:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn _update_revision(uuid: &GroupId, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult {
|
||||
db_run! {conn: {
|
||||
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))
|
||||
@@ -310,7 +306,7 @@ impl Group {
|
||||
}
|
||||
|
||||
impl CollectionGroup {
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await;
|
||||
for group_user in group_users {
|
||||
group_user.update_user_revision(conn).await;
|
||||
@@ -369,17 +365,16 @@ impl CollectionGroup {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_group(group_uuid: &GroupId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_group(group_uuid: &GroupId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
collections_groups::table
|
||||
.filter(collections_groups::groups_uuid.eq(group_uuid))
|
||||
.load::<CollectionGroupDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collection groups")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
collections_groups::table
|
||||
.inner_join(groups_users::table.on(
|
||||
@@ -390,24 +385,22 @@ impl CollectionGroup {
|
||||
))
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.select(collections_groups::all_columns)
|
||||
.load::<CollectionGroupDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user collection groups")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_collection(collection_uuid: &CollectionId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
collections_groups::table
|
||||
.filter(collections_groups::collections_uuid.eq(collection_uuid))
|
||||
.select(collections_groups::all_columns)
|
||||
.load::<CollectionGroupDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading collection groups")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await;
|
||||
for group_user in group_users {
|
||||
group_user.update_user_revision(conn).await;
|
||||
@@ -422,7 +415,7 @@ impl CollectionGroup {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_group(group_uuid: &GroupId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_group(group_uuid: &GroupId, conn: &DbConn) -> EmptyResult {
|
||||
let group_users = GroupUser::find_by_group(group_uuid, conn).await;
|
||||
for group_user in group_users {
|
||||
group_user.update_user_revision(conn).await;
|
||||
@@ -436,7 +429,7 @@ impl CollectionGroup {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_collection(collection_uuid: &CollectionId, conn: &DbConn) -> EmptyResult {
|
||||
let collection_assigned_to_groups = CollectionGroup::find_by_collection(collection_uuid, conn).await;
|
||||
for collection_assigned_to_group in collection_assigned_to_groups {
|
||||
let group_users = GroupUser::find_by_group(&collection_assigned_to_group.groups_uuid, conn).await;
|
||||
@@ -455,7 +448,7 @@ impl CollectionGroup {
|
||||
}
|
||||
|
||||
impl GroupUser {
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
self.update_user_revision(conn).await;
|
||||
|
||||
db_run! { conn:
|
||||
@@ -501,30 +494,28 @@ impl GroupUser {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_group(group_uuid: &GroupId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_group(group_uuid: &GroupId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
groups_users::table
|
||||
.filter(groups_users::groups_uuid.eq(group_uuid))
|
||||
.load::<GroupUserDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading group users")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_member(member_uuid: &MembershipId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_member(member_uuid: &MembershipId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
groups_users::table
|
||||
.filter(groups_users::users_organizations_uuid.eq(member_uuid))
|
||||
.load::<GroupUserDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading groups for user")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn has_access_to_collection_by_member(
|
||||
collection_uuid: &CollectionId,
|
||||
member_uuid: &MembershipId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> bool {
|
||||
db_run! { conn: {
|
||||
groups_users::table
|
||||
@@ -542,7 +533,7 @@ impl GroupUser {
|
||||
pub async fn has_full_access_by_member(
|
||||
org_uuid: &OrganizationId,
|
||||
member_uuid: &MembershipId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> bool {
|
||||
db_run! { conn: {
|
||||
groups_users::table
|
||||
@@ -558,7 +549,7 @@ impl GroupUser {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn update_user_revision(&self, conn: &mut DbConn) {
|
||||
pub async fn update_user_revision(&self, conn: &DbConn) {
|
||||
match Membership::find_by_uuid(&self.users_organizations_uuid, conn).await {
|
||||
Some(member) => User::update_uuid_revision(&member.user_uuid, conn).await,
|
||||
None => warn!("Member could not be found!"),
|
||||
@@ -568,7 +559,7 @@ impl GroupUser {
|
||||
pub async fn delete_by_group_and_member(
|
||||
group_uuid: &GroupId,
|
||||
member_uuid: &MembershipId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
match Membership::find_by_uuid(member_uuid, conn).await {
|
||||
Some(member) => User::update_uuid_revision(&member.user_uuid, conn).await,
|
||||
@@ -584,7 +575,7 @@ impl GroupUser {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_group(group_uuid: &GroupId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_group(group_uuid: &GroupId, conn: &DbConn) -> EmptyResult {
|
||||
let group_users = GroupUser::find_by_group(group_uuid, conn).await;
|
||||
for group_user in group_users {
|
||||
group_user.update_user_revision(conn).await;
|
||||
@@ -598,7 +589,7 @@ impl GroupUser {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_member(member_uuid: &MembershipId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_member(member_uuid: &MembershipId, conn: &DbConn) -> EmptyResult {
|
||||
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!"),
|
||||
|
||||
@@ -3,22 +3,22 @@ use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::db::schema::{org_policies, users_organizations};
|
||||
use crate::db::DbConn;
|
||||
use crate::error::MapResult;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use super::{Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId, TwoFactor, UserId};
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = org_policies)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct OrgPolicy {
|
||||
pub uuid: OrgPolicyId,
|
||||
pub org_uuid: OrganizationId,
|
||||
pub atype: i32,
|
||||
pub enabled: bool,
|
||||
pub data: String,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = org_policies)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct OrgPolicy {
|
||||
pub uuid: OrgPolicyId,
|
||||
pub org_uuid: OrganizationId,
|
||||
pub atype: i32,
|
||||
pub enabled: bool,
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Enums/PolicyType.cs
|
||||
@@ -107,11 +107,11 @@ impl OrgPolicy {
|
||||
|
||||
/// Database methods
|
||||
impl OrgPolicy {
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(org_policies::table)
|
||||
.values(OrgPolicyDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -119,7 +119,7 @@ impl OrgPolicy {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(org_policies::table)
|
||||
.filter(org_policies::uuid.eq(&self.uuid))
|
||||
.set(OrgPolicyDb::to_db(self))
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving org_policy")
|
||||
}
|
||||
@@ -127,7 +127,6 @@ impl OrgPolicy {
|
||||
}.map_res("Error saving org_policy")
|
||||
}
|
||||
postgresql {
|
||||
let value = OrgPolicyDb::to_db(self);
|
||||
// We need to make sure we're not going to violate the unique constraint on org_uuid and atype.
|
||||
// This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
|
||||
// not support multiple constraints on ON CONFLICT clauses.
|
||||
@@ -140,17 +139,17 @@ impl OrgPolicy {
|
||||
.map_res("Error deleting org_policy for insert")?;
|
||||
|
||||
diesel::insert_into(org_policies::table)
|
||||
.values(&value)
|
||||
.values(self)
|
||||
.on_conflict(org_policies::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving org_policy")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(org_policies::table.filter(org_policies::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
@@ -158,17 +157,16 @@ impl OrgPolicy {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
org_policies::table
|
||||
.filter(org_policies::org_uuid.eq(org_uuid))
|
||||
.load::<OrgPolicyDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading org_policy")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
org_policies::table
|
||||
.inner_join(
|
||||
@@ -180,28 +178,26 @@ impl OrgPolicy {
|
||||
users_organizations::status.eq(MembershipStatus::Confirmed as i32)
|
||||
)
|
||||
.select(org_policies::all_columns)
|
||||
.load::<OrgPolicyDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading org_policy")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_org_and_type(
|
||||
org_uuid: &OrganizationId,
|
||||
policy_type: OrgPolicyType,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
org_policies::table
|
||||
.filter(org_policies::org_uuid.eq(org_uuid))
|
||||
.filter(org_policies::atype.eq(policy_type as i32))
|
||||
.first::<OrgPolicyDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid)))
|
||||
.execute(conn)
|
||||
@@ -230,16 +226,15 @@ impl OrgPolicy {
|
||||
.filter(org_policies::atype.eq(policy_type as i32))
|
||||
.filter(org_policies::enabled.eq(true))
|
||||
.select(org_policies::all_columns)
|
||||
.load::<OrgPolicyDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading org_policy")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_user_and_active_policy(
|
||||
user_uuid: &UserId,
|
||||
policy_type: OrgPolicyType,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
org_policies::table
|
||||
@@ -254,9 +249,8 @@ impl OrgPolicy {
|
||||
.filter(org_policies::atype.eq(policy_type as i32))
|
||||
.filter(org_policies::enabled.eq(true))
|
||||
.select(org_policies::all_columns)
|
||||
.load::<OrgPolicyDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading org_policy")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -267,7 +261,7 @@ impl OrgPolicy {
|
||||
user_uuid: &UserId,
|
||||
policy_type: OrgPolicyType,
|
||||
exclude_org_uuid: Option<&OrganizationId>,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> bool {
|
||||
for policy in
|
||||
OrgPolicy::find_accepted_and_confirmed_by_user_and_active_policy(user_uuid, policy_type, conn).await
|
||||
@@ -290,7 +284,7 @@ impl OrgPolicy {
|
||||
user_uuid: &UserId,
|
||||
org_uuid: &OrganizationId,
|
||||
exclude_current_org: bool,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> OrgPolicyResult {
|
||||
// Enforce TwoFactor/TwoStep login
|
||||
if TwoFactor::find_by_user(user_uuid, conn).await.is_empty() {
|
||||
@@ -316,7 +310,7 @@ impl OrgPolicy {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn org_is_reset_password_auto_enroll(org_uuid: &OrganizationId, conn: &mut DbConn) -> bool {
|
||||
pub async fn org_is_reset_password_auto_enroll(org_uuid: &OrganizationId, conn: &DbConn) -> bool {
|
||||
match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await {
|
||||
Some(policy) => match serde_json::from_str::<ResetPasswordDataModel>(&policy.data) {
|
||||
Ok(opts) => {
|
||||
@@ -332,7 +326,7 @@ impl OrgPolicy {
|
||||
|
||||
/// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
|
||||
/// option of the `Send Options` policy, and the user is not an owner or admin of that org.
|
||||
pub async fn is_hide_email_disabled(user_uuid: &UserId, conn: &mut DbConn) -> bool {
|
||||
pub async fn is_hide_email_disabled(user_uuid: &UserId, conn: &DbConn) -> bool {
|
||||
for policy in
|
||||
OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await
|
||||
{
|
||||
@@ -352,11 +346,7 @@ impl OrgPolicy {
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn is_enabled_for_member(
|
||||
member_uuid: &MembershipId,
|
||||
policy_type: OrgPolicyType,
|
||||
conn: &mut DbConn,
|
||||
) -> bool {
|
||||
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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use num_traits::FromPrimitive;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
@@ -11,51 +12,53 @@ use super::{
|
||||
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;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = organizations)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Organization {
|
||||
pub uuid: OrganizationId,
|
||||
pub name: String,
|
||||
pub billing_email: String,
|
||||
pub private_key: Option<String>,
|
||||
pub public_key: Option<String>,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = organizations)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Organization {
|
||||
pub uuid: OrganizationId,
|
||||
pub name: String,
|
||||
pub billing_email: String,
|
||||
pub private_key: Option<String>,
|
||||
pub public_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = users_organizations)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Membership {
|
||||
pub uuid: MembershipId,
|
||||
pub user_uuid: UserId,
|
||||
pub org_uuid: OrganizationId,
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = users_organizations)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Membership {
|
||||
pub uuid: MembershipId,
|
||||
pub user_uuid: UserId,
|
||||
pub org_uuid: OrganizationId,
|
||||
|
||||
pub invited_by_email: Option<String>,
|
||||
pub invited_by_email: Option<String>,
|
||||
|
||||
pub access_all: bool,
|
||||
pub akey: String,
|
||||
pub status: i32,
|
||||
pub atype: i32,
|
||||
pub reset_password_key: Option<String>,
|
||||
pub external_id: Option<String>,
|
||||
}
|
||||
pub access_all: bool,
|
||||
pub akey: String,
|
||||
pub status: i32,
|
||||
pub atype: i32,
|
||||
pub reset_password_key: Option<String>,
|
||||
pub external_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = organization_api_key)]
|
||||
#[diesel(primary_key(uuid, org_uuid))]
|
||||
pub struct OrganizationApiKey {
|
||||
pub uuid: OrgApiKeyId,
|
||||
pub org_uuid: OrganizationId,
|
||||
pub atype: i32,
|
||||
pub api_key: String,
|
||||
pub revision_date: NaiveDateTime,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = organization_api_key)]
|
||||
#[diesel(primary_key(uuid, org_uuid))]
|
||||
pub struct OrganizationApiKey {
|
||||
pub uuid: OrgApiKeyId,
|
||||
pub org_uuid: OrganizationId,
|
||||
pub atype: i32,
|
||||
pub api_key: String,
|
||||
pub revision_date: NaiveDateTime,
|
||||
}
|
||||
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Enums/OrganizationUserStatusType.cs
|
||||
@@ -325,7 +328,7 @@ use crate::error::MapResult;
|
||||
|
||||
/// Database methods
|
||||
impl Organization {
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
if !crate::util::is_valid_email(&self.billing_email) {
|
||||
err!(format!("BillingEmail {} is not a valid email address", self.billing_email))
|
||||
}
|
||||
@@ -337,7 +340,7 @@ impl Organization {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(organizations::table)
|
||||
.values(OrganizationDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -345,7 +348,7 @@ impl Organization {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(organizations::table)
|
||||
.filter(organizations::uuid.eq(&self.uuid))
|
||||
.set(OrganizationDb::to_db(self))
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving organization")
|
||||
}
|
||||
@@ -354,19 +357,18 @@ impl Organization {
|
||||
|
||||
}
|
||||
postgresql {
|
||||
let value = OrganizationDb::to_db(self);
|
||||
diesel::insert_into(organizations::table)
|
||||
.values(&value)
|
||||
.values(self)
|
||||
.on_conflict(organizations::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving organization")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
use super::{Cipher, Collection};
|
||||
|
||||
Cipher::delete_all_by_organization(&self.uuid, conn).await?;
|
||||
@@ -383,31 +385,33 @@ impl Organization {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &OrganizationId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_uuid(uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
organizations::table
|
||||
.filter(organizations::uuid.eq(uuid))
|
||||
.first::<OrganizationDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_name(name: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_name(name: &str, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
organizations::table
|
||||
.filter(organizations::name.eq(name))
|
||||
.first::<OrganizationDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn get_all(conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn get_all(conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
organizations::table.load::<OrganizationDb>(conn).expect("Error loading organizations").from_db()
|
||||
organizations::table
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading organizations")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_main_org_user_email(user_email: &str, conn: &mut DbConn) -> Option<Organization> {
|
||||
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: {
|
||||
@@ -418,12 +422,12 @@ impl Organization {
|
||||
.filter(users_organizations::status.ne(MembershipStatus::Revoked as i32))
|
||||
.order(users_organizations::atype.asc())
|
||||
.select(organizations::all_columns)
|
||||
.first::<OrganizationDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_org_user_email(user_email: &str, conn: &mut DbConn) -> Vec<Organization> {
|
||||
pub async fn find_org_user_email(user_email: &str, conn: &DbConn) -> Vec<Self> {
|
||||
let lower_mail = user_email.to_lowercase();
|
||||
|
||||
db_run! { conn: {
|
||||
@@ -434,15 +438,14 @@ impl Organization {
|
||||
.filter(users_organizations::status.ne(MembershipStatus::Revoked as i32))
|
||||
.order(users_organizations::atype.asc())
|
||||
.select(organizations::all_columns)
|
||||
.load::<OrganizationDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user orgs")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
impl Membership {
|
||||
pub async fn to_json(&self, conn: &mut DbConn) -> Value {
|
||||
pub async fn to_json(&self, conn: &DbConn) -> Value {
|
||||
let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap();
|
||||
|
||||
// HACK: Convert the manager type to a custom type
|
||||
@@ -533,12 +536,7 @@ impl Membership {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn to_json_user_details(
|
||||
&self,
|
||||
include_collections: bool,
|
||||
include_groups: bool,
|
||||
conn: &mut DbConn,
|
||||
) -> Value {
|
||||
pub async fn to_json_user_details(&self, include_collections: bool, include_groups: bool, conn: &DbConn) -> Value {
|
||||
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
|
||||
|
||||
// Because BitWarden want the status to be -1 for revoked users we need to catch that here.
|
||||
@@ -680,7 +678,7 @@ impl Membership {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn to_json_details(&self, conn: &mut DbConn) -> Value {
|
||||
pub async fn to_json_details(&self, conn: &DbConn) -> Value {
|
||||
let coll_uuids = if self.access_all {
|
||||
vec![] // If we have complete access, no need to fill the array
|
||||
} else {
|
||||
@@ -720,7 +718,7 @@ impl Membership {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn to_json_mini_details(&self, conn: &mut DbConn) -> Value {
|
||||
pub async fn to_json_mini_details(&self, conn: &DbConn) -> Value {
|
||||
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
|
||||
|
||||
// Because Bitwarden wants the status to be -1 for revoked users we need to catch that here.
|
||||
@@ -742,13 +740,13 @@ impl Membership {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(&self.user_uuid, conn).await;
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(users_organizations::table)
|
||||
.values(MembershipDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -756,7 +754,7 @@ impl Membership {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(users_organizations::table)
|
||||
.filter(users_organizations::uuid.eq(&self.uuid))
|
||||
.set(MembershipDb::to_db(self))
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error adding user to organization")
|
||||
},
|
||||
@@ -764,19 +762,18 @@ impl Membership {
|
||||
}.map_res("Error adding user to organization")
|
||||
}
|
||||
postgresql {
|
||||
let value = MembershipDb::to_db(self);
|
||||
diesel::insert_into(users_organizations::table)
|
||||
.values(&value)
|
||||
.values(self)
|
||||
.on_conflict(users_organizations::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error adding user to organization")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
User::update_uuid_revision(&self.user_uuid, conn).await;
|
||||
|
||||
CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?;
|
||||
@@ -789,25 +786,21 @@ impl Membership {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
for member in Self::find_by_org(org_uuid, conn).await {
|
||||
member.delete(conn).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
for member in Self::find_any_state_by_user(user_uuid, conn).await {
|
||||
member.delete(conn).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_email_and_org(
|
||||
email: &str,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
) -> Option<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);
|
||||
@@ -829,52 +822,48 @@ impl Membership {
|
||||
(self.access_all || self.atype >= MembershipType::Admin) && self.has_status(MembershipStatus::Confirmed)
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &MembershipId, conn: &mut DbConn) -> Option<Self> {
|
||||
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::<MembershipDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_org(
|
||||
uuid: &MembershipId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
) -> Option<Self> {
|
||||
pub async fn find_by_uuid_and_org(uuid: &MembershipId, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::uuid.eq(uuid))
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.first::<MembershipDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_confirmed_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.load::<MembershipDb>(conn)
|
||||
.unwrap_or_default().from_db()
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_invited_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_invited_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Invited as i32))
|
||||
.load::<MembershipDb>(conn)
|
||||
.unwrap_or_default().from_db()
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
|
||||
// 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: &mut DbConn) -> EmptyResult {
|
||||
pub async fn accept_user_invitations(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::update(users_organizations::table)
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
@@ -885,16 +874,16 @@ impl Membership {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_any_state_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_any_state_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.load::<MembershipDb>(conn)
|
||||
.unwrap_or_default().from_db()
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_accepted_and_confirmed_by_user(user_uuid: &UserId, conn: &mut DbConn) -> i64 {
|
||||
pub async fn count_accepted_and_confirmed_by_user(user_uuid: &UserId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
@@ -905,27 +894,27 @@ impl Membership {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.load::<MembershipDb>(conn)
|
||||
.expect("Error loading user organizations").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_confirmed_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.load::<MembershipDb>(conn)
|
||||
.unwrap_or_default().from_db()
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
|
||||
// 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: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_confirmed_and_manage_all_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
@@ -934,12 +923,12 @@ impl Membership {
|
||||
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::<MembershipDb>(conn)
|
||||
.unwrap_or_default().from_db()
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
@@ -950,24 +939,20 @@ impl Membership {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_org_and_type(
|
||||
org_uuid: &OrganizationId,
|
||||
atype: MembershipType,
|
||||
conn: &mut DbConn,
|
||||
) -> Vec<Self> {
|
||||
pub async fn find_by_org_and_type(org_uuid: &OrganizationId, atype: MembershipType, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.filter(users_organizations::atype.eq(atype as i32))
|
||||
.load::<MembershipDb>(conn)
|
||||
.expect("Error loading user organizations").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_confirmed_by_org_and_type(
|
||||
org_uuid: &OrganizationId,
|
||||
atype: MembershipType,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> i64 {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
@@ -980,24 +965,20 @@ impl Membership {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_org(
|
||||
user_uuid: &UserId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
) -> Option<Self> {
|
||||
pub async fn find_by_user_and_org(user_uuid: &UserId, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.first::<MembershipDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_confirmed_by_user_and_org(
|
||||
user_uuid: &UserId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
@@ -1006,21 +987,21 @@ impl Membership {
|
||||
.filter(
|
||||
users_organizations::status.eq(MembershipStatus::Confirmed as i32)
|
||||
)
|
||||
.first::<MembershipDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
.load::<MembershipDb>(conn)
|
||||
.expect("Error loading user organizations").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn get_orgs_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<OrganizationId> {
|
||||
pub async fn get_orgs_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<OrganizationId> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||
@@ -1030,11 +1011,7 @@ impl Membership {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_policy(
|
||||
user_uuid: &UserId,
|
||||
policy_type: OrgPolicyType,
|
||||
conn: &mut DbConn,
|
||||
) -> Vec<Self> {
|
||||
pub async fn find_by_user_and_policy(user_uuid: &UserId, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.inner_join(
|
||||
@@ -1048,16 +1025,12 @@ impl Membership {
|
||||
users_organizations::status.eq(MembershipStatus::Confirmed as i32)
|
||||
)
|
||||
.select(users_organizations::all_columns)
|
||||
.load::<MembershipDb>(conn)
|
||||
.unwrap_or_default().from_db()
|
||||
.load::<Self>(conn)
|
||||
.unwrap_or_default()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_cipher_and_org(
|
||||
cipher_uuid: &CipherId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
) -> Vec<Self> {
|
||||
pub async fn find_by_cipher_and_org(cipher_uuid: &CipherId, org_uuid: &OrganizationId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
@@ -1076,14 +1049,15 @@ impl Membership {
|
||||
)
|
||||
.select(users_organizations::all_columns)
|
||||
.distinct()
|
||||
.load::<MembershipDb>(conn).expect("Error loading user organizations").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_cipher_and_org_with_group(
|
||||
cipher_uuid: &CipherId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
@@ -1106,15 +1080,12 @@ impl Membership {
|
||||
)
|
||||
.select(users_organizations::all_columns)
|
||||
.distinct()
|
||||
.load::<MembershipDb>(conn).expect("Error loading user organizations with groups").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations with groups")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn user_has_ge_admin_access_to_cipher(
|
||||
user_uuid: &UserId,
|
||||
cipher_uuid: &CipherId,
|
||||
conn: &mut DbConn,
|
||||
) -> bool {
|
||||
pub async fn user_has_ge_admin_access_to_cipher(user_uuid: &UserId, cipher_uuid: &CipherId, conn: &DbConn) -> bool {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.inner_join(ciphers::table.on(ciphers::uuid.eq(cipher_uuid).and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))))
|
||||
@@ -1122,14 +1093,15 @@ impl Membership {
|
||||
.filter(users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32]))
|
||||
.count()
|
||||
.first::<i64>(conn)
|
||||
.ok().unwrap_or(0) != 0
|
||||
.ok()
|
||||
.unwrap_or(0) != 0
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_collection_and_org(
|
||||
collection_uuid: &CollectionId,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
@@ -1143,33 +1115,31 @@ impl Membership {
|
||||
)
|
||||
)
|
||||
.select(users_organizations::all_columns)
|
||||
.load::<MembershipDb>(conn).expect("Error loading user organizations").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading user organizations")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_external_id_and_org(
|
||||
ext_id: &str,
|
||||
org_uuid: &OrganizationId,
|
||||
conn: &mut DbConn,
|
||||
) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_by_external_id_and_org(ext_id: &str, org_uuid: &OrganizationId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(
|
||||
users_organizations::external_id.eq(ext_id)
|
||||
.and(users_organizations::org_uuid.eq(org_uuid))
|
||||
)
|
||||
.first::<MembershipDb>(conn).ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_main_user_org(user_uuid: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_main_user_org(user_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { 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::<MembershipDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1179,7 +1149,7 @@ impl OrganizationApiKey {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(organization_api_key::table)
|
||||
.values(OrganizationApiKeyDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -1187,7 +1157,7 @@ impl OrganizationApiKey {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(organization_api_key::table)
|
||||
.filter(organization_api_key::uuid.eq(&self.uuid))
|
||||
.set(OrganizationApiKeyDb::to_db(self))
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving organization")
|
||||
}
|
||||
@@ -1196,12 +1166,11 @@ impl OrganizationApiKey {
|
||||
|
||||
}
|
||||
postgresql {
|
||||
let value = OrganizationApiKeyDb::to_db(self);
|
||||
diesel::insert_into(organization_api_key::table)
|
||||
.values(&value)
|
||||
.values(self)
|
||||
.on_conflict((organization_api_key::uuid, organization_api_key::org_uuid))
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving organization")
|
||||
}
|
||||
@@ -1212,12 +1181,12 @@ impl OrganizationApiKey {
|
||||
db_run! { conn: {
|
||||
organization_api_key::table
|
||||
.filter(organization_api_key::org_uuid.eq(org_uuid))
|
||||
.first::<OrganizationApiKeyDb>(conn)
|
||||
.ok().from_db()
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_organization(org_uuid: &OrganizationId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(organization_api_key::table.filter(organization_api_key::org_uuid.eq(org_uuid)))
|
||||
.execute(conn)
|
||||
|
||||
@@ -4,40 +4,40 @@ use serde_json::Value;
|
||||
use crate::{config::PathType, util::LowerCase, CONFIG};
|
||||
|
||||
use super::{OrganizationId, User, UserId};
|
||||
use crate::db::schema::sends;
|
||||
use diesel::prelude::*;
|
||||
use id::SendId;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = sends)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Send {
|
||||
pub uuid: SendId,
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = sends)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct Send {
|
||||
pub uuid: SendId,
|
||||
|
||||
pub user_uuid: Option<UserId>,
|
||||
pub organization_uuid: Option<OrganizationId>,
|
||||
pub user_uuid: Option<UserId>,
|
||||
pub organization_uuid: Option<OrganizationId>,
|
||||
|
||||
pub name: String,
|
||||
pub notes: Option<String>,
|
||||
pub name: String,
|
||||
pub notes: Option<String>,
|
||||
|
||||
pub atype: i32,
|
||||
pub data: String,
|
||||
pub akey: String,
|
||||
pub password_hash: Option<Vec<u8>>,
|
||||
password_salt: Option<Vec<u8>>,
|
||||
password_iter: Option<i32>,
|
||||
pub atype: i32,
|
||||
pub data: String,
|
||||
pub akey: String,
|
||||
pub password_hash: Option<Vec<u8>>,
|
||||
password_salt: Option<Vec<u8>>,
|
||||
password_iter: Option<i32>,
|
||||
|
||||
pub max_access_count: Option<i32>,
|
||||
pub access_count: i32,
|
||||
pub max_access_count: Option<i32>,
|
||||
pub access_count: i32,
|
||||
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub revision_date: NaiveDateTime,
|
||||
pub expiration_date: Option<NaiveDateTime>,
|
||||
pub deletion_date: NaiveDateTime,
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub revision_date: NaiveDateTime,
|
||||
pub expiration_date: Option<NaiveDateTime>,
|
||||
pub deletion_date: NaiveDateTime,
|
||||
|
||||
pub disabled: bool,
|
||||
pub hide_email: Option<bool>,
|
||||
}
|
||||
pub disabled: bool,
|
||||
pub hide_email: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)]
|
||||
@@ -103,7 +103,7 @@ impl Send {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn creator_identifier(&self, conn: &mut DbConn) -> Option<String> {
|
||||
pub async fn creator_identifier(&self, conn: &DbConn) -> Option<String> {
|
||||
if let Some(hide_email) = self.hide_email {
|
||||
if hide_email {
|
||||
return None;
|
||||
@@ -155,7 +155,7 @@ impl Send {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn to_json_access(&self, conn: &mut DbConn) -> Value {
|
||||
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();
|
||||
@@ -187,14 +187,14 @@ use crate::error::MapResult;
|
||||
use crate::util::NumberOrString;
|
||||
|
||||
impl Send {
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
self.update_users_revision(conn).await;
|
||||
self.revision_date = Utc::now().naive_utc();
|
||||
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(sends::table)
|
||||
.values(SendDb::to_db(self))
|
||||
.values(&*self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -202,7 +202,7 @@ impl Send {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(sends::table)
|
||||
.filter(sends::uuid.eq(&self.uuid))
|
||||
.set(SendDb::to_db(self))
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving send")
|
||||
}
|
||||
@@ -210,19 +210,18 @@ impl Send {
|
||||
}.map_res("Error saving send")
|
||||
}
|
||||
postgresql {
|
||||
let value = SendDb::to_db(self);
|
||||
diesel::insert_into(sends::table)
|
||||
.values(&value)
|
||||
.values(&*self)
|
||||
.on_conflict(sends::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving send")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
self.update_users_revision(conn).await;
|
||||
|
||||
if self.atype == SendType::File as i32 {
|
||||
@@ -238,13 +237,13 @@ impl Send {
|
||||
}
|
||||
|
||||
/// Purge all sends that are past their deletion date.
|
||||
pub async fn purge(conn: &mut DbConn) {
|
||||
pub async fn purge(conn: &DbConn) {
|
||||
for send in Self::find_by_past_deletion_date(conn).await {
|
||||
send.delete(conn).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec<UserId> {
|
||||
pub async fn update_users_revision(&self, conn: &DbConn) -> Vec<UserId> {
|
||||
let mut user_uuids = Vec::new();
|
||||
match &self.user_uuid {
|
||||
Some(user_uuid) => {
|
||||
@@ -258,14 +257,14 @@ impl Send {
|
||||
user_uuids
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
for send in Self::find_by_user(user_uuid, conn).await {
|
||||
send.delete(conn).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_access_id(access_id: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_access_id(access_id: &str, conn: &DbConn) -> Option<Self> {
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -281,36 +280,35 @@ impl Send {
|
||||
Self::find_by_uuid(&uuid, conn).await
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &SendId, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_by_uuid(uuid: &SendId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
sends::table
|
||||
.filter(sends::uuid.eq(uuid))
|
||||
.first::<SendDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid_and_user(uuid: &SendId, user_uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
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::<SendDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
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::<SendDb>(conn).expect("Error loading sends").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading sends")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn size_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Option<i64> {
|
||||
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)]
|
||||
@@ -333,20 +331,22 @@ impl Send {
|
||||
Some(total)
|
||||
}
|
||||
|
||||
pub async fn find_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
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::<SendDb>(conn).expect("Error loading sends").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading sends")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_past_deletion_date(conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_past_deletion_date(conn: &DbConn) -> Vec<Self> {
|
||||
let now = Utc::now().naive_utc();
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
sends::table
|
||||
.filter(sends::deletion_date.lt(now))
|
||||
.load::<SendDb>(conn).expect("Error loading sends").from_db()
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading sends")
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::db::schema::sso_nonce;
|
||||
use crate::db::{DbConn, DbPool};
|
||||
use crate::error::MapResult;
|
||||
use crate::sso::{OIDCState, NONCE_EXPIRATION};
|
||||
use diesel::prelude::*;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = sso_nonce)]
|
||||
#[diesel(primary_key(state))]
|
||||
pub struct SsoNonce {
|
||||
pub state: OIDCState,
|
||||
pub nonce: String,
|
||||
pub verifier: Option<String>,
|
||||
pub redirect_uri: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = sso_nonce)]
|
||||
#[diesel(primary_key(state))]
|
||||
pub struct SsoNonce {
|
||||
pub state: OIDCState,
|
||||
pub nonce: String,
|
||||
pub verifier: Option<String>,
|
||||
pub redirect_uri: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
/// Local methods
|
||||
@@ -35,25 +35,24 @@ impl SsoNonce {
|
||||
|
||||
/// Database methods
|
||||
impl SsoNonce {
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
diesel::replace_into(sso_nonce::table)
|
||||
.values(SsoNonceDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving SSO nonce")
|
||||
}
|
||||
postgresql {
|
||||
let value = SsoNonceDb::to_db(self);
|
||||
diesel::insert_into(sso_nonce::table)
|
||||
.values(&value)
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving SSO nonce")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(state: &OIDCState, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(state: &OIDCState, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(sso_nonce::table.filter(sso_nonce::state.eq(state)))
|
||||
.execute(conn)
|
||||
@@ -67,9 +66,8 @@ impl SsoNonce {
|
||||
sso_nonce::table
|
||||
.filter(sso_nonce::state.eq(state))
|
||||
.filter(sso_nonce::created_at.ge(oldest))
|
||||
.first::<SsoNonceDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
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};
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = twofactor)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct TwoFactor {
|
||||
pub uuid: TwoFactorId,
|
||||
pub user_uuid: UserId,
|
||||
pub atype: i32,
|
||||
pub enabled: bool,
|
||||
pub data: String,
|
||||
pub last_used: i64,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = twofactor)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct TwoFactor {
|
||||
pub uuid: TwoFactorId,
|
||||
pub user_uuid: UserId,
|
||||
pub atype: i32,
|
||||
pub enabled: bool,
|
||||
pub data: String,
|
||||
pub last_used: i64,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -76,11 +76,11 @@ impl TwoFactor {
|
||||
|
||||
/// Database methods
|
||||
impl TwoFactor {
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(twofactor::table)
|
||||
.values(TwoFactorDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
@@ -88,7 +88,7 @@ impl TwoFactor {
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(twofactor::table)
|
||||
.filter(twofactor::uuid.eq(&self.uuid))
|
||||
.set(TwoFactorDb::to_db(self))
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving twofactor")
|
||||
}
|
||||
@@ -96,7 +96,6 @@ impl TwoFactor {
|
||||
}.map_res("Error saving twofactor")
|
||||
}
|
||||
postgresql {
|
||||
let value = TwoFactorDb::to_db(self);
|
||||
// We need to make sure we're not going to violate the unique constraint on user_uuid and atype.
|
||||
// This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
|
||||
// not support multiple constraints on ON CONFLICT clauses.
|
||||
@@ -105,17 +104,17 @@ impl TwoFactor {
|
||||
.map_res("Error deleting twofactor for insert")?;
|
||||
|
||||
diesel::insert_into(twofactor::table)
|
||||
.values(&value)
|
||||
.values(self)
|
||||
.on_conflict(twofactor::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving twofactor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(twofactor::table.filter(twofactor::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
@@ -123,29 +122,27 @@ impl TwoFactor {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
twofactor::table
|
||||
.filter(twofactor::user_uuid.eq(user_uuid))
|
||||
.filter(twofactor::atype.lt(1000)) // Filter implementation types
|
||||
.load::<TwoFactorDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_type(user_uuid: &UserId, atype: i32, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_user_and_type(user_uuid: &UserId, atype: i32, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
twofactor::table
|
||||
.filter(twofactor::user_uuid.eq(user_uuid))
|
||||
.filter(twofactor::atype.eq(atype))
|
||||
.first::<TwoFactorDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
@@ -153,13 +150,12 @@ impl TwoFactor {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn migrate_u2f_to_webauthn(conn: &mut DbConn) -> EmptyResult {
|
||||
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::<TwoFactorDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor")
|
||||
.from_db()
|
||||
}};
|
||||
|
||||
use crate::api::core::two_factor::webauthn::U2FRegistration;
|
||||
@@ -231,13 +227,12 @@ impl TwoFactor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_credential_to_passkey(conn: &mut DbConn) -> EmptyResult {
|
||||
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::<TwoFactorDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor")
|
||||
.from_db()
|
||||
}};
|
||||
|
||||
for webauthn_factor in webauthn_factors {
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
use chrono::Utc;
|
||||
|
||||
use crate::db::schema::twofactor_duo_ctx;
|
||||
use crate::{api::EmptyResult, db::DbConn, error::MapResult};
|
||||
use diesel::prelude::*;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = twofactor_duo_ctx)]
|
||||
#[diesel(primary_key(state))]
|
||||
pub struct TwoFactorDuoContext {
|
||||
pub state: String,
|
||||
pub user_email: String,
|
||||
pub nonce: String,
|
||||
pub exp: i64,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = twofactor_duo_ctx)]
|
||||
#[diesel(primary_key(state))]
|
||||
pub struct TwoFactorDuoContext {
|
||||
pub state: String,
|
||||
pub user_email: String,
|
||||
pub nonce: String,
|
||||
pub exp: i64,
|
||||
}
|
||||
|
||||
impl TwoFactorDuoContext {
|
||||
pub async fn find_by_state(state: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! {
|
||||
conn: {
|
||||
twofactor_duo_ctx::table
|
||||
.filter(twofactor_duo_ctx::state.eq(state))
|
||||
.first::<TwoFactorDuoContextDb>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}
|
||||
}
|
||||
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()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn save(state: &str, user_email: &str, nonce: &str, ttl: i64, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(state: &str, user_email: &str, nonce: &str, ttl: i64, conn: &DbConn) -> EmptyResult {
|
||||
// A saved context should never be changed, only created or deleted.
|
||||
let exists = Self::find_by_state(state, conn).await;
|
||||
if exists.is_some() {
|
||||
@@ -36,47 +33,40 @@ impl TwoFactorDuoContext {
|
||||
|
||||
let exp = Utc::now().timestamp() + ttl;
|
||||
|
||||
db_run! {
|
||||
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")
|
||||
}
|
||||
}
|
||||
db_run! { 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")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_expired(conn: &mut DbConn) -> Vec<Self> {
|
||||
pub async fn find_expired(conn: &DbConn) -> Vec<Self> {
|
||||
let now = Utc::now().timestamp();
|
||||
db_run! {
|
||||
conn: {
|
||||
db_run! { conn: {
|
||||
twofactor_duo_ctx::table
|
||||
.filter(twofactor_duo_ctx::exp.lt(now))
|
||||
.load::<Self>(conn)
|
||||
.expect("Error finding expired contexts in twofactor_duo_ctx")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(
|
||||
twofactor_duo_ctx::table
|
||||
.filter(twofactor_duo_ctx::exp.lt(now))
|
||||
.load::<TwoFactorDuoContextDb>(conn)
|
||||
.expect("Error finding expired contexts in twofactor_duo_ctx")
|
||||
.from_db()
|
||||
}
|
||||
}
|
||||
.filter(twofactor_duo_ctx::state.eq(&self.state)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting from twofactor_duo_ctx")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
db_run! {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn purge_expired_duo_contexts(conn: &mut DbConn) {
|
||||
pub async fn purge_expired_duo_contexts(conn: &DbConn) {
|
||||
for context in Self::find_expired(conn).await {
|
||||
context.delete(conn).await.ok();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
|
||||
use crate::db::schema::twofactor_incomplete;
|
||||
use crate::{
|
||||
api::EmptyResult,
|
||||
auth::ClientIp,
|
||||
@@ -10,22 +11,21 @@ use crate::{
|
||||
error::MapResult,
|
||||
CONFIG,
|
||||
};
|
||||
use diesel::prelude::*;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = twofactor_incomplete)]
|
||||
#[diesel(primary_key(user_uuid, device_uuid))]
|
||||
pub struct TwoFactorIncomplete {
|
||||
pub user_uuid: UserId,
|
||||
// This device UUID is simply what's claimed by the device. It doesn't
|
||||
// necessarily correspond to any UUID in the devices table, since a device
|
||||
// must complete 2FA login before being added into the devices table.
|
||||
pub device_uuid: DeviceId,
|
||||
pub device_name: String,
|
||||
pub device_type: i32,
|
||||
pub login_time: NaiveDateTime,
|
||||
pub ip_address: String,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
#[diesel(table_name = twofactor_incomplete)]
|
||||
#[diesel(primary_key(user_uuid, device_uuid))]
|
||||
pub struct TwoFactorIncomplete {
|
||||
pub user_uuid: UserId,
|
||||
// This device UUID is simply what's claimed by the device. It doesn't
|
||||
// necessarily correspond to any UUID in the devices table, since a device
|
||||
// must complete 2FA login before being added into the devices table.
|
||||
pub device_uuid: DeviceId,
|
||||
pub device_name: String,
|
||||
pub device_type: i32,
|
||||
pub login_time: NaiveDateTime,
|
||||
pub ip_address: String,
|
||||
}
|
||||
|
||||
impl TwoFactorIncomplete {
|
||||
@@ -35,7 +35,7 @@ impl TwoFactorIncomplete {
|
||||
device_name: &str,
|
||||
device_type: i32,
|
||||
ip: &ClientIp,
|
||||
conn: &mut DbConn,
|
||||
conn: &DbConn,
|
||||
) -> EmptyResult {
|
||||
if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() {
|
||||
return Ok(());
|
||||
@@ -64,7 +64,7 @@ impl TwoFactorIncomplete {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn mark_complete(user_uuid: &UserId, device_uuid: &DeviceId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn mark_complete(user_uuid: &UserId, device_uuid: &DeviceId, conn: &DbConn) -> EmptyResult {
|
||||
if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -72,40 +72,30 @@ impl TwoFactorIncomplete {
|
||||
Self::delete_by_user_and_device(user_uuid, device_uuid, conn).await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_and_device(
|
||||
user_uuid: &UserId,
|
||||
device_uuid: &DeviceId,
|
||||
conn: &mut DbConn,
|
||||
) -> Option<Self> {
|
||||
pub async fn find_by_user_and_device(user_uuid: &UserId, device_uuid: &DeviceId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
twofactor_incomplete::table
|
||||
.filter(twofactor_incomplete::user_uuid.eq(user_uuid))
|
||||
.filter(twofactor_incomplete::device_uuid.eq(device_uuid))
|
||||
.first::<TwoFactorIncompleteDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_logins_before(dt: &NaiveDateTime, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_logins_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
twofactor_incomplete::table
|
||||
.filter(twofactor_incomplete::login_time.lt(dt))
|
||||
.load::<TwoFactorIncompleteDb>(conn)
|
||||
.load::<Self>(conn)
|
||||
.expect("Error loading twofactor_incomplete")
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
Self::delete_by_user_and_device(&self.user_uuid, &self.device_uuid, conn).await
|
||||
}
|
||||
|
||||
pub async fn delete_by_user_and_device(
|
||||
user_uuid: &UserId,
|
||||
device_uuid: &DeviceId,
|
||||
conn: &mut DbConn,
|
||||
) -> EmptyResult {
|
||||
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))
|
||||
@@ -115,7 +105,7 @@ impl TwoFactorIncomplete {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(twofactor_incomplete::table.filter(twofactor_incomplete::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::db::schema::{devices, invitations, sso_users, users};
|
||||
use chrono::{NaiveDateTime, TimeDelta, Utc};
|
||||
use derive_more::{AsRef, Deref, Display, From};
|
||||
use diesel::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{
|
||||
@@ -17,70 +19,68 @@ use crate::{
|
||||
};
|
||||
use macros::UuidFromParam;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Selectable)]
|
||||
#[diesel(table_name = users)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct User {
|
||||
pub uuid: UserId,
|
||||
pub enabled: bool,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub verified_at: Option<NaiveDateTime>,
|
||||
pub last_verifying_at: Option<NaiveDateTime>,
|
||||
pub login_verify_count: i32,
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Selectable)]
|
||||
#[diesel(table_name = users)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct User {
|
||||
pub uuid: UserId,
|
||||
pub enabled: bool,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub verified_at: Option<NaiveDateTime>,
|
||||
pub last_verifying_at: Option<NaiveDateTime>,
|
||||
pub login_verify_count: i32,
|
||||
|
||||
pub email: String,
|
||||
pub email_new: Option<String>,
|
||||
pub email_new_token: Option<String>,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub email_new: Option<String>,
|
||||
pub email_new_token: Option<String>,
|
||||
pub name: String,
|
||||
|
||||
pub password_hash: Vec<u8>,
|
||||
pub salt: Vec<u8>,
|
||||
pub password_iterations: i32,
|
||||
pub password_hint: Option<String>,
|
||||
pub password_hash: Vec<u8>,
|
||||
pub salt: Vec<u8>,
|
||||
pub password_iterations: i32,
|
||||
pub password_hint: Option<String>,
|
||||
|
||||
pub akey: String,
|
||||
pub private_key: Option<String>,
|
||||
pub public_key: Option<String>,
|
||||
pub akey: String,
|
||||
pub private_key: Option<String>,
|
||||
pub public_key: Option<String>,
|
||||
|
||||
#[diesel(column_name = "totp_secret")] // Note, this is only added to the UserDb structs, not to User
|
||||
_totp_secret: Option<String>,
|
||||
pub totp_recover: Option<String>,
|
||||
#[diesel(column_name = "totp_secret")] // Note, this is only added to the UserDb structs, not to User
|
||||
_totp_secret: Option<String>,
|
||||
pub totp_recover: Option<String>,
|
||||
|
||||
pub security_stamp: String,
|
||||
pub stamp_exception: Option<String>,
|
||||
pub security_stamp: String,
|
||||
pub stamp_exception: Option<String>,
|
||||
|
||||
pub equivalent_domains: String,
|
||||
pub excluded_globals: String,
|
||||
pub equivalent_domains: String,
|
||||
pub excluded_globals: String,
|
||||
|
||||
pub client_kdf_type: i32,
|
||||
pub client_kdf_iter: i32,
|
||||
pub client_kdf_memory: Option<i32>,
|
||||
pub client_kdf_parallelism: Option<i32>,
|
||||
pub client_kdf_type: i32,
|
||||
pub client_kdf_iter: i32,
|
||||
pub client_kdf_memory: Option<i32>,
|
||||
pub client_kdf_parallelism: Option<i32>,
|
||||
|
||||
pub api_key: Option<String>,
|
||||
pub api_key: Option<String>,
|
||||
|
||||
pub avatar_color: Option<String>,
|
||||
pub avatar_color: Option<String>,
|
||||
|
||||
pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore.
|
||||
}
|
||||
pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore.
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = invitations)]
|
||||
#[diesel(primary_key(email))]
|
||||
pub struct Invitation {
|
||||
pub email: String,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable)]
|
||||
#[diesel(table_name = invitations)]
|
||||
#[diesel(primary_key(email))]
|
||||
pub struct Invitation {
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable, Insertable, Selectable)]
|
||||
#[diesel(table_name = sso_users)]
|
||||
#[diesel(primary_key(user_uuid))]
|
||||
pub struct SsoUser {
|
||||
pub user_uuid: UserId,
|
||||
pub identifier: OIDCIdentifier,
|
||||
}
|
||||
#[derive(Identifiable, Queryable, Insertable, Selectable)]
|
||||
#[diesel(table_name = sso_users)]
|
||||
#[diesel(primary_key(user_uuid))]
|
||||
pub struct SsoUser {
|
||||
pub user_uuid: UserId,
|
||||
pub identifier: OIDCIdentifier,
|
||||
}
|
||||
|
||||
pub enum UserKdfType {
|
||||
@@ -236,7 +236,7 @@ impl User {
|
||||
|
||||
/// Database methods
|
||||
impl User {
|
||||
pub async fn to_json(&self, conn: &mut DbConn) -> Value {
|
||||
pub async fn to_json(&self, conn: &DbConn) -> Value {
|
||||
let mut orgs_json = Vec::new();
|
||||
for c in Membership::find_confirmed_by_user(&self.uuid, conn).await {
|
||||
orgs_json.push(c.to_json(conn).await);
|
||||
@@ -275,38 +275,36 @@ impl User {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||
if !crate::util::is_valid_email(&self.email) {
|
||||
err!(format!("User email {} is not a valid email address", self.email))
|
||||
}
|
||||
|
||||
self.updated_at = Utc::now().naive_utc();
|
||||
|
||||
db_run! {conn:
|
||||
db_run! { conn:
|
||||
mysql {
|
||||
let value = UserDb::to_db(self);
|
||||
diesel::insert_into(users::table)
|
||||
.values(&value)
|
||||
.values(&*self)
|
||||
.on_conflict(diesel::dsl::DuplicatedKeys)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving user")
|
||||
}
|
||||
postgresql, sqlite {
|
||||
let value = UserDb::to_db(self);
|
||||
diesel::insert_into(users::table) // Insert or update
|
||||
.values(&value)
|
||||
.values(&*self)
|
||||
.on_conflict(users::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.set(&*self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving user")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
for member in Membership::find_confirmed_by_user(&self.uuid, conn).await {
|
||||
if member.atype == MembershipType::Owner
|
||||
&& Membership::count_confirmed_by_org_and_type(&member.org_uuid, MembershipType::Owner, conn).await <= 1
|
||||
@@ -327,23 +325,23 @@ impl User {
|
||||
TwoFactorIncomplete::delete_all_by_user(&self.uuid, conn).await?;
|
||||
Invitation::take(&self.email, conn).await; // Delete invitation if any
|
||||
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
diesel::delete(users::table.filter(users::uuid.eq(self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting user")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn update_uuid_revision(uuid: &UserId, conn: &mut DbConn) {
|
||||
pub async fn update_uuid_revision(uuid: &UserId, conn: &DbConn) {
|
||||
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
|
||||
warn!("Failed to update revision for {uuid}: {e:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_all_revisions(conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn update_all_revisions(conn: &DbConn) -> EmptyResult {
|
||||
let updated_at = Utc::now().naive_utc();
|
||||
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
retry(|| {
|
||||
diesel::update(users::table)
|
||||
.set(users::updated_at.eq(updated_at))
|
||||
@@ -353,14 +351,14 @@ impl User {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn update_revision(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
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
|
||||
}
|
||||
|
||||
async fn _update_revision(uuid: &UserId, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult {
|
||||
db_run! {conn: {
|
||||
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))
|
||||
@@ -370,49 +368,49 @@ impl User {
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_mail(mail: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
|
||||
let lower_mail = mail.to_lowercase();
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
users::table
|
||||
.filter(users::email.eq(lower_mail))
|
||||
.first::<UserDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &UserId, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
users::table.filter(users::uuid.eq(uuid)).first::<UserDb>(conn).ok().from_db()
|
||||
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()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_device_id(device_uuid: &DeviceId, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_device_id(device_uuid: &DeviceId, conn: &DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
users::table
|
||||
.inner_join(devices::table.on(devices::user_uuid.eq(users::uuid)))
|
||||
.filter(devices::uuid.eq(device_uuid))
|
||||
.select(users::all_columns)
|
||||
.first::<UserDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn get_all(conn: &mut DbConn) -> Vec<(User, Option<SsoUser>)> {
|
||||
db_run! {conn: {
|
||||
pub async fn get_all(conn: &DbConn) -> Vec<(Self, Option<SsoUser>)> {
|
||||
db_run! { conn: {
|
||||
users::table
|
||||
.left_join(sso_users::table)
|
||||
.select(<(UserDb, Option<SsoUserDb>)>::as_select())
|
||||
.select(<(Self, Option<SsoUser>)>::as_select())
|
||||
.load(conn)
|
||||
.expect("Error loading groups for user")
|
||||
.into_iter()
|
||||
.map(|(user, sso_user)| { (user.from_db(), sso_user.from_db()) })
|
||||
.collect()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn last_active(&self, conn: &mut DbConn) -> Option<NaiveDateTime> {
|
||||
pub async fn last_active(&self, conn: &DbConn) -> Option<NaiveDateTime> {
|
||||
match Device::find_latest_active_by_user(&self.uuid, conn).await {
|
||||
Some(device) => Some(device.updated_at),
|
||||
None => None,
|
||||
@@ -428,23 +426,23 @@ impl Invitation {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
if !crate::util::is_valid_email(&self.email) {
|
||||
err!(format!("Invitation email {} is not a valid email address", self.email))
|
||||
}
|
||||
|
||||
db_run! {conn:
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
// Not checking for ForeignKey Constraints here
|
||||
// Table invitations does not have any ForeignKey Constraints.
|
||||
diesel::replace_into(invitations::table)
|
||||
.values(InvitationDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving invitation")
|
||||
}
|
||||
postgresql {
|
||||
diesel::insert_into(invitations::table)
|
||||
.values(InvitationDb::to_db(self))
|
||||
.values(self)
|
||||
.on_conflict(invitations::email)
|
||||
.do_nothing()
|
||||
.execute(conn)
|
||||
@@ -453,26 +451,25 @@ impl Invitation {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult {
|
||||
db_run! {conn: {
|
||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(invitations::table.filter(invitations::email.eq(self.email)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting invitation")
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_mail(mail: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
|
||||
let lower_mail = mail.to_lowercase();
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
invitations::table
|
||||
.filter(invitations::email.eq(lower_mail))
|
||||
.first::<InvitationDb>(conn)
|
||||
.first::<Self>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn take(mail: &str, conn: &mut DbConn) -> bool {
|
||||
pub async fn take(mail: &str, conn: &DbConn) -> bool {
|
||||
match Self::find_by_mail(mail, conn).await {
|
||||
Some(invitation) => invitation.delete(conn).await.is_ok(),
|
||||
None => false,
|
||||
@@ -501,52 +498,49 @@ impl Invitation {
|
||||
pub struct UserId(String);
|
||||
|
||||
impl SsoUser {
|
||||
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
diesel::replace_into(sso_users::table)
|
||||
.values(SsoUserDb::to_db(self))
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving SSO user")
|
||||
}
|
||||
postgresql {
|
||||
let value = SsoUserDb::to_db(self);
|
||||
diesel::insert_into(sso_users::table)
|
||||
.values(&value)
|
||||
.values(self)
|
||||
.execute(conn)
|
||||
.map_res("Error saving SSO user")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_identifier(identifier: &str, conn: &DbConn) -> Option<(User, SsoUser)> {
|
||||
db_run! {conn: {
|
||||
pub async fn find_by_identifier(identifier: &str, conn: &DbConn) -> Option<(User, Self)> {
|
||||
db_run! { conn: {
|
||||
users::table
|
||||
.inner_join(sso_users::table)
|
||||
.select(<(UserDb, SsoUserDb)>::as_select())
|
||||
.select(<(User, Self)>::as_select())
|
||||
.filter(sso_users::identifier.eq(identifier))
|
||||
.first::<(UserDb, SsoUserDb)>(conn)
|
||||
.first::<(User, Self)>(conn)
|
||||
.ok()
|
||||
.map(|(user, sso_user)| { (user.from_db(), sso_user.from_db()) })
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<(User, Option<SsoUser>)> {
|
||||
pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<(User, Option<Self>)> {
|
||||
let lower_mail = mail.to_lowercase();
|
||||
|
||||
db_run! {conn: {
|
||||
db_run! { conn: {
|
||||
users::table
|
||||
.left_join(sso_users::table)
|
||||
.select(<(UserDb, Option<SsoUserDb>)>::as_select())
|
||||
.select(<(User, Option<Self>)>::as_select())
|
||||
.filter(users::email.eq(lower_mail))
|
||||
.first::<(UserDb, Option<SsoUserDb>)>(conn)
|
||||
.first::<(User, Option<Self>)>(conn)
|
||||
.ok()
|
||||
.map(|(user, sso_user)| { (user.from_db(), sso_user.from_db()) })
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete(user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult {
|
||||
db_run! {conn: {
|
||||
pub async fn delete(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(sso_users::table.filter(sso_users::user_uuid.eq(user_uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting sso user")
|
||||
|
||||
Reference in New Issue
Block a user