geforkt von mirrored/vaultwarden
Add support for multiple simultaneous database features by using macros.
Diesel requires the following changes: - Separate connection and pool types per connection, the generate_connections! macro generates an enum with a variant per db type - Separate migrations and schemas, these were always imported as one type depending on db feature, now they are all imported under different module names - Separate model objects per connection, the db_object! macro generates one object for each connection with the diesel macros, a generic object, and methods to convert between the connection-specific and the generic ones - Separate connection queries, the db_run! macro allows writing only one that gets compiled for all databases or multiple ones
Dieser Commit ist enthalten in:
Ursprung
19889187a5
Commit
0365b7c6a4
17 geänderte Dateien mit 1506 neuen und 1148 gelöschten Zeilen
33
Cargo.lock
generiert
33
Cargo.lock
generiert
|
@ -152,6 +152,7 @@ dependencies = [
|
||||||
"oath",
|
"oath",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"openssl",
|
"openssl",
|
||||||
|
"paste",
|
||||||
"percent-encoding 2.1.0",
|
"percent-encoding 2.1.0",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"regex",
|
"regex",
|
||||||
|
@ -274,9 +275,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.13"
|
version = "0.4.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6"
|
checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
@ -295,9 +296,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "2.33.2"
|
version = "2.33.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10040cdf04294b565d9e0319955430099ec3813a64c952b86a41200ad714ae48"
|
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"atty",
|
"atty",
|
||||||
|
@ -781,14 +782,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "handlebars"
|
name = "handlebars"
|
||||||
version = "3.3.0"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86dbc8a0746b08f363d2e00da48e6c9ceb75c198ac692d2715fcbb5bee74c87d"
|
checksum = "5deefd4816fb852b1ff3cb48f6c41da67be2d0e1d20b26a7a3b076da11f064b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log 0.4.11",
|
"log 0.4.11",
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"quick-error",
|
"quick-error 2.0.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
@ -1360,7 +1361,7 @@ dependencies = [
|
||||||
"log 0.4.11",
|
"log 0.4.11",
|
||||||
"mime 0.3.16",
|
"mime 0.3.16",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"quick-error",
|
"quick-error 1.2.3",
|
||||||
"rand 0.6.5",
|
"rand 0.6.5",
|
||||||
"safemem",
|
"safemem",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
@ -1519,9 +1520,9 @@ checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.4.0"
|
version = "1.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
|
checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
|
@ -1628,6 +1629,12 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6ddc8e145de01d9180ac7b78b9676f95a9c2447f6a88b2c2a04702211bc5d71"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pear"
|
name = "pear"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
@ -1873,6 +1880,12 @@ version = "1.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "0.6.13"
|
version = "0.6.13"
|
||||||
|
|
|
@ -123,6 +123,9 @@ structopt = "0.3.16"
|
||||||
# Logging panics to logfile instead stderr only
|
# Logging panics to logfile instead stderr only
|
||||||
backtrace = "0.3.50"
|
backtrace = "0.3.50"
|
||||||
|
|
||||||
|
# Macro ident concatenation
|
||||||
|
paste = "1.0"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# Use newest ring
|
# Use newest ring
|
||||||
rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '1010f6a2a88fac899dec0cd2f642156908038a53' }
|
rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '1010f6a2a88fac899dec0cd2f642156908038a53' }
|
||||||
|
|
13
build.rs
13
build.rs
|
@ -2,12 +2,13 @@ use std::process::Command;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(all(feature = "sqlite", feature = "mysql"))]
|
// This allow using #[cfg(sqlite)] instead of #[cfg(feature = "sqlite")], which helps when trying to add them through macros
|
||||||
compile_error!("Can't enable both sqlite and mysql at the same time");
|
#[cfg(feature = "sqlite")]
|
||||||
#[cfg(all(feature = "sqlite", feature = "postgresql"))]
|
println!("cargo:rustc-cfg=sqlite");
|
||||||
compile_error!("Can't enable both sqlite and postgresql at the same time");
|
#[cfg(feature = "mysql")]
|
||||||
#[cfg(all(feature = "mysql", feature = "postgresql"))]
|
println!("cargo:rustc-cfg=mysql");
|
||||||
compile_error!("Can't enable both mysql and postgresql at the same time");
|
#[cfg(feature = "postgresql")]
|
||||||
|
println!("cargo:rustc-cfg=postgresql");
|
||||||
|
|
||||||
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgresql")))]
|
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgresql")))]
|
||||||
compile_error!("You need to enable one DB backend. To build with previous defaults do: cargo build --features sqlite");
|
compile_error!("You need to enable one DB backend. To build with previous defaults do: cargo build --features sqlite");
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
api::{ApiResult, EmptyResult, JsonResult},
|
api::{ApiResult, EmptyResult, JsonResult},
|
||||||
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
|
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
|
||||||
config::ConfigBuilder,
|
config::ConfigBuilder,
|
||||||
db::{backup_database, models::*, DbConn},
|
db::{backup_database, models::*, DbConn, DbConnType},
|
||||||
error::{Error, MapResult},
|
error::{Error, MapResult},
|
||||||
mail,
|
mail,
|
||||||
util::get_display_size,
|
util::get_display_size,
|
||||||
|
@ -48,8 +48,12 @@ pub fn routes() -> Vec<Route> {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
static CAN_BACKUP: Lazy<bool> =
|
static CAN_BACKUP: Lazy<bool> = Lazy::new(|| {
|
||||||
Lazy::new(|| cfg!(feature = "sqlite") && Command::new("sqlite3").arg("-version").status().is_ok());
|
DbConnType::from_url(&CONFIG.database_url())
|
||||||
|
.map(|t| t == DbConnType::sqlite)
|
||||||
|
.unwrap_or(false)
|
||||||
|
&& Command::new("sqlite3").arg("-version").status().is_ok()
|
||||||
|
});
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn admin_disabled() -> &'static str {
|
fn admin_disabled() -> &'static str {
|
||||||
|
|
|
@ -5,6 +5,7 @@ use once_cell::sync::Lazy;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
db::DbConnType,
|
||||||
error::Error,
|
error::Error,
|
||||||
util::{get_env, get_env_bool},
|
util::{get_env, get_env_bool},
|
||||||
};
|
};
|
||||||
|
@ -421,20 +422,9 @@ make_config! {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
||||||
let db_url = cfg.database_url.to_lowercase();
|
|
||||||
if cfg!(feature = "sqlite")
|
|
||||||
&& (db_url.starts_with("mysql:") || db_url.starts_with("postgresql:") || db_url.starts_with("postgres:"))
|
|
||||||
{
|
|
||||||
err!("`DATABASE_URL` is meant for MySQL or Postgres, while this server is meant for SQLite")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg!(feature = "mysql") && !db_url.starts_with("mysql:") {
|
// Validate connection URL is valid and DB feature is enabled
|
||||||
err!("`DATABASE_URL` should start with mysql: when using the MySQL server")
|
DbConnType::from_url(&cfg.database_url)?;
|
||||||
}
|
|
||||||
|
|
||||||
if cfg!(feature = "postgresql") && !(db_url.starts_with("postgresql:") || db_url.starts_with("postgres:")) {
|
|
||||||
err!("`DATABASE_URL` should start with postgresql: when using the PostgreSQL server")
|
|
||||||
}
|
|
||||||
|
|
||||||
let dom = cfg.domain.to_lowercase();
|
let dom = cfg.domain.to_lowercase();
|
||||||
if !dom.starts_with("http://") && !dom.starts_with("https://") {
|
if !dom.starts_with("http://") && !dom.starts_with("https://") {
|
||||||
|
|
305
src/db/mod.rs
305
src/db/mod.rs
|
@ -1,51 +1,203 @@
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use diesel::{r2d2, r2d2::ConnectionManager, Connection as DieselConnection, ConnectionError};
|
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
http::Status,
|
http::Status,
|
||||||
request::{FromRequest, Outcome},
|
request::{FromRequest, Outcome},
|
||||||
Request, State,
|
Request, State,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{error::Error, CONFIG};
|
use crate::{
|
||||||
|
error::{Error, MapResult},
|
||||||
|
CONFIG,
|
||||||
|
};
|
||||||
|
|
||||||
/// An alias to the database connection used
|
#[cfg(sqlite)]
|
||||||
#[cfg(feature = "sqlite")]
|
|
||||||
type Connection = diesel::sqlite::SqliteConnection;
|
|
||||||
#[cfg(feature = "mysql")]
|
|
||||||
type Connection = diesel::mysql::MysqlConnection;
|
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
type Connection = diesel::pg::PgConnection;
|
|
||||||
|
|
||||||
/// An alias to the type for a pool of Diesel connections.
|
|
||||||
type Pool = r2d2::Pool<ConnectionManager<Connection>>;
|
|
||||||
|
|
||||||
/// Connection request guard type: a wrapper around an r2d2 pooled connection.
|
|
||||||
pub struct DbConn(pub r2d2::PooledConnection<ConnectionManager<Connection>>);
|
|
||||||
|
|
||||||
pub mod models;
|
|
||||||
#[cfg(feature = "sqlite")]
|
|
||||||
#[path = "schemas/sqlite/schema.rs"]
|
#[path = "schemas/sqlite/schema.rs"]
|
||||||
pub mod schema;
|
pub mod __sqlite_schema;
|
||||||
#[cfg(feature = "mysql")]
|
|
||||||
|
#[cfg(mysql)]
|
||||||
#[path = "schemas/mysql/schema.rs"]
|
#[path = "schemas/mysql/schema.rs"]
|
||||||
pub mod schema;
|
pub mod __mysql_schema;
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
|
#[cfg(postgresql)]
|
||||||
#[path = "schemas/postgresql/schema.rs"]
|
#[path = "schemas/postgresql/schema.rs"]
|
||||||
pub mod schema;
|
pub mod __postgresql_schema;
|
||||||
|
|
||||||
/// Initializes a database pool.
|
|
||||||
pub fn init_pool() -> Pool {
|
|
||||||
let manager = ConnectionManager::new(CONFIG.database_url());
|
|
||||||
|
|
||||||
r2d2::Pool::builder().build(manager).expect("Failed to create pool")
|
// This is used to generate the main DbConn and DbPool enums, which contain one variant for each database supported
|
||||||
|
macro_rules! generate_connections {
|
||||||
|
( $( $name:ident: $ty:ty ),+ ) => {
|
||||||
|
#[allow(non_camel_case_types, dead_code)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub enum DbConnType { $( $name, )+ }
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum DbConn { $( #[cfg($name)] $name(PooledConnection<ConnectionManager< $ty >>), )+ }
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum DbPool { $( #[cfg($name)] $name(Pool<ConnectionManager< $ty >>), )+ }
|
||||||
|
|
||||||
|
impl DbPool {
|
||||||
|
// For the given database URL, guess it's type, run migrations create pool and return it
|
||||||
|
pub fn from_config() -> Result<Self, Error> {
|
||||||
|
let url = CONFIG.database_url();
|
||||||
|
let conn_type = DbConnType::from_url(&url)?;
|
||||||
|
|
||||||
|
match conn_type { $(
|
||||||
|
DbConnType::$name => {
|
||||||
|
#[cfg($name)]
|
||||||
|
{
|
||||||
|
paste::paste!{ [< $name _migrations >]::run_migrations(); }
|
||||||
|
let manager = ConnectionManager::new(&url);
|
||||||
|
let pool = Pool::builder().build(manager).map_res("Failed to create pool")?;
|
||||||
|
return Ok(Self::$name(pool));
|
||||||
|
}
|
||||||
|
#[cfg(not($name))]
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
return unreachable!("Trying to use a DB backend when it's feature is disabled");
|
||||||
|
},
|
||||||
|
)+ }
|
||||||
|
}
|
||||||
|
// Get a connection from the pool
|
||||||
|
pub fn get(&self) -> Result<DbConn, Error> {
|
||||||
|
match self { $(
|
||||||
|
#[cfg($name)]
|
||||||
|
Self::$name(p) => Ok(DbConn::$name(p.get().map_res("Error retrieving connection from pool")?)),
|
||||||
|
)+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_connection() -> Result<Connection, ConnectionError> {
|
generate_connections! {
|
||||||
Connection::establish(&CONFIG.database_url())
|
sqlite: diesel::sqlite::SqliteConnection,
|
||||||
|
mysql: diesel::mysql::MysqlConnection,
|
||||||
|
postgresql: diesel::pg::PgConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DbConnType {
|
||||||
|
pub fn from_url(url: &str) -> Result<DbConnType, Error> {
|
||||||
|
// Mysql
|
||||||
|
if url.starts_with("mysql:") {
|
||||||
|
#[cfg(mysql)]
|
||||||
|
return Ok(DbConnType::mysql);
|
||||||
|
|
||||||
|
#[cfg(not(mysql))]
|
||||||
|
err!("`DATABASE_URL` is a MySQL URL, but the 'mysql' feature is not enabled")
|
||||||
|
|
||||||
|
// Postgres
|
||||||
|
} else if url.starts_with("postgresql:") || url.starts_with("postgres:") {
|
||||||
|
#[cfg(postgresql)]
|
||||||
|
return Ok(DbConnType::postgresql);
|
||||||
|
|
||||||
|
#[cfg(not(postgresql))]
|
||||||
|
err!("`DATABASE_URL` is a PostgreSQL URL, but the 'postgresql' feature is not enabled")
|
||||||
|
|
||||||
|
//Sqlite
|
||||||
|
} else {
|
||||||
|
#[cfg(sqlite)]
|
||||||
|
return Ok(DbConnType::sqlite);
|
||||||
|
|
||||||
|
#[cfg(not(sqlite))]
|
||||||
|
err!("`DATABASE_URL` looks like a SQLite URL, but 'sqlite' feature is not enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! db_run {
|
||||||
|
// Same for all dbs
|
||||||
|
( $conn:ident: $body:block ) => {
|
||||||
|
db_run! { $conn: sqlite, mysql, postgresql $body }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Different code for each db
|
||||||
|
( $conn:ident: $( $($db:ident),+ $body:block )+ ) => {
|
||||||
|
#[allow(unused)] use diesel::prelude::*;
|
||||||
|
match $conn {
|
||||||
|
$($(
|
||||||
|
#[cfg($db)]
|
||||||
|
crate::db::DbConn::$db(ref $conn) => {
|
||||||
|
paste::paste! {
|
||||||
|
#[allow(unused)] use crate::db::[<__ $db _schema>]::{self as schema, *};
|
||||||
|
#[allow(unused)] use [<__ $db _model>]::*;
|
||||||
|
#[allow(unused)] use crate::db::FromDb;
|
||||||
|
}
|
||||||
|
$body
|
||||||
|
},
|
||||||
|
)+)+
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub trait FromDb {
|
||||||
|
type Output;
|
||||||
|
fn from_db(self) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each struct eg. Cipher, we create a CipherDb inside a module named __$db_model (where $db is sqlite, mysql or postgresql),
|
||||||
|
// to implement the Diesel traits. We also provide methods to convert between them and the basic structs. Later, that module will be auto imported when using db_run!
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! db_object {
|
||||||
|
( $(
|
||||||
|
$( #[$attr:meta] )*
|
||||||
|
pub struct $name:ident {
|
||||||
|
$( $( #[$field_attr:meta] )* $vis:vis $field:ident : $typ:ty ),+
|
||||||
|
$(,)?
|
||||||
|
}
|
||||||
|
)+ ) => {
|
||||||
|
// Create the normal struct, without attributes
|
||||||
|
$( pub struct $name { $( /*$( #[$field_attr] )**/ $vis $field : $typ, )+ } )+
|
||||||
|
|
||||||
|
#[cfg(sqlite)]
|
||||||
|
pub mod __sqlite_model { $( db_object! { @db sqlite | $( #[$attr] )* | $name | $( $( #[$field_attr] )* $field : $typ ),+ } )+ }
|
||||||
|
#[cfg(mysql)]
|
||||||
|
pub mod __mysql_model { $( db_object! { @db mysql | $( #[$attr] )* | $name | $( $( #[$field_attr] )* $field : $typ ),+ } )+ }
|
||||||
|
#[cfg(postgresql)]
|
||||||
|
pub mod __postgresql_model { $( db_object! { @db postgresql | $( #[$attr] )* | $name | $( $( #[$field_attr] )* $field : $typ ),+ } )+ }
|
||||||
|
};
|
||||||
|
|
||||||
|
( @db $db:ident | $( #[$attr:meta] )* | $name:ident | $( $( #[$field_attr:meta] )* $vis:vis $field:ident : $typ:ty),+) => {
|
||||||
|
paste::paste! {
|
||||||
|
#[allow(unused)] use super::*;
|
||||||
|
#[allow(unused)] use diesel::prelude::*;
|
||||||
|
#[allow(unused)] use crate::db::[<__ $db _schema>]::*;
|
||||||
|
|
||||||
|
$( #[$attr] )*
|
||||||
|
pub struct [<$name Db>] { $(
|
||||||
|
$( #[$field_attr] )* $vis $field : $typ,
|
||||||
|
)+ }
|
||||||
|
|
||||||
|
impl [<$name Db>] {
|
||||||
|
#[inline(always)] pub fn from_db(self) -> super::$name { super::$name { $( $field: self.$field, )+ } }
|
||||||
|
#[inline(always)] pub fn to_db(x: &super::$name) -> Self { Self { $( $field: x.$field.clone(), )+ } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::db::FromDb for [<$name Db>] {
|
||||||
|
type Output = super::$name;
|
||||||
|
#[inline(always)] fn from_db(self) -> Self::Output { super::$name { $( $field: self.$field, )+ } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::db::FromDb for Vec<[<$name Db>]> {
|
||||||
|
type Output = Vec<super::$name>;
|
||||||
|
#[inline(always)] fn from_db(self) -> Self::Output { self.into_iter().map(crate::db::FromDb::from_db).collect() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::db::FromDb for Option<[<$name Db>]> {
|
||||||
|
type Output = Option<super::$name>;
|
||||||
|
#[inline(always)] fn from_db(self) -> Self::Output { self.map(crate::db::FromDb::from_db) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reexport the models, needs to be after the macros are defined so it can access them
|
||||||
|
pub mod models;
|
||||||
|
|
||||||
/// Creates a back-up of the database using sqlite3
|
/// Creates a back-up of the database using sqlite3
|
||||||
pub fn backup_database() -> Result<(), Error> {
|
pub fn backup_database() -> Result<(), Error> {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -73,18 +225,99 @@ impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> Outcome<DbConn, ()> {
|
fn from_request(request: &'a Request<'r>) -> Outcome<DbConn, ()> {
|
||||||
// https://github.com/SergioBenitez/Rocket/commit/e3c1a4ad3ab9b840482ec6de4200d30df43e357c
|
// https://github.com/SergioBenitez/Rocket/commit/e3c1a4ad3ab9b840482ec6de4200d30df43e357c
|
||||||
let pool = try_outcome!(request.guard::<State<Pool>>());
|
let pool = try_outcome!(request.guard::<State<DbPool>>());
|
||||||
match pool.get() {
|
match pool.get() {
|
||||||
Ok(conn) => Outcome::Success(DbConn(conn)),
|
Ok(conn) => Outcome::Success(conn),
|
||||||
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
|
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the convenience of using an &DbConn as a &Database.
|
// Embed the migrations from the migrations folder into the application
|
||||||
impl std::ops::Deref for DbConn {
|
// This way, the program automatically migrates the database to the latest version
|
||||||
type Target = Connection;
|
// https://docs.rs/diesel_migrations/*/diesel_migrations/macro.embed_migrations.html
|
||||||
fn deref(&self) -> &Self::Target {
|
#[cfg(sqlite)]
|
||||||
&self.0
|
mod sqlite_migrations {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
embed_migrations!("migrations/sqlite");
|
||||||
|
|
||||||
|
pub fn run_migrations() {
|
||||||
|
// Make sure the directory exists
|
||||||
|
let url = crate::CONFIG.database_url();
|
||||||
|
let path = std::path::Path::new(&url);
|
||||||
|
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
if std::fs::create_dir_all(parent).is_err() {
|
||||||
|
error!("Error creating database directory");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use diesel::{Connection, RunQueryDsl};
|
||||||
|
// Make sure the database is up to date (create if it doesn't exist, or run the migrations)
|
||||||
|
let connection =
|
||||||
|
diesel::sqlite::SqliteConnection::establish(&crate::CONFIG.database_url()).expect("Can't connect to DB");
|
||||||
|
// Disable Foreign Key Checks during migration
|
||||||
|
|
||||||
|
// Scoped to a connection.
|
||||||
|
diesel::sql_query("PRAGMA foreign_keys = OFF")
|
||||||
|
.execute(&connection)
|
||||||
|
.expect("Failed to disable Foreign Key Checks during migrations");
|
||||||
|
|
||||||
|
// Turn on WAL in SQLite
|
||||||
|
if crate::CONFIG.enable_db_wal() {
|
||||||
|
diesel::sql_query("PRAGMA journal_mode=wal")
|
||||||
|
.execute(&connection)
|
||||||
|
.expect("Failed to turn on WAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
embedded_migrations::run_with_output(&connection, &mut std::io::stdout()).expect("Can't run migrations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(mysql)]
|
||||||
|
mod mysql_migrations {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
embed_migrations!("migrations/mysql");
|
||||||
|
|
||||||
|
pub fn run_migrations() {
|
||||||
|
use diesel::{Connection, RunQueryDsl};
|
||||||
|
// Make sure the database is up to date (create if it doesn't exist, or run the migrations)
|
||||||
|
let connection =
|
||||||
|
diesel::mysql::MysqlConnection::establish(&crate::CONFIG.database_url()).expect("Can't connect to DB");
|
||||||
|
// Disable Foreign Key Checks during migration
|
||||||
|
|
||||||
|
// Scoped to a connection/session.
|
||||||
|
diesel::sql_query("SET FOREIGN_KEY_CHECKS = 0")
|
||||||
|
.execute(&connection)
|
||||||
|
.expect("Failed to disable Foreign Key Checks during migrations");
|
||||||
|
|
||||||
|
embedded_migrations::run_with_output(&connection, &mut std::io::stdout()).expect("Can't run migrations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(postgresql)]
|
||||||
|
mod postgresql_migrations {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
embed_migrations!("migrations/postgresql");
|
||||||
|
|
||||||
|
pub fn run_migrations() {
|
||||||
|
use diesel::{Connection, RunQueryDsl};
|
||||||
|
// Make sure the database is up to date (create if it doesn't exist, or run the migrations)
|
||||||
|
let connection =
|
||||||
|
diesel::pg::PgConnection::establish(&crate::CONFIG.database_url()).expect("Can't connect to DB");
|
||||||
|
// Disable Foreign Key Checks during migration
|
||||||
|
|
||||||
|
// FIXME: Per https://www.postgresql.org/docs/12/sql-set-constraints.html,
|
||||||
|
// "SET CONSTRAINTS sets the behavior of constraint checking within the
|
||||||
|
// current transaction", so this setting probably won't take effect for
|
||||||
|
// any of the migrations since it's being run outside of a transaction.
|
||||||
|
// Migrations that need to disable foreign key checks should run this
|
||||||
|
// from within the migration script itself.
|
||||||
|
diesel::sql_query("SET CONSTRAINTS ALL DEFERRED")
|
||||||
|
.execute(&connection)
|
||||||
|
.expect("Failed to disable Foreign Key Checks during migrations");
|
||||||
|
|
||||||
|
embedded_migrations::run_with_output(&connection, &mut std::io::stdout()).expect("Can't run migrations");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,19 @@ use serde_json::Value;
|
||||||
use super::Cipher;
|
use super::Cipher;
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
db_object! {
|
||||||
#[table_name = "attachments"]
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
||||||
#[changeset_options(treat_none_as_null="true")]
|
#[table_name = "attachments"]
|
||||||
#[belongs_to(Cipher, foreign_key = "cipher_uuid")]
|
#[changeset_options(treat_none_as_null="true")]
|
||||||
#[primary_key(id)]
|
#[belongs_to(super::Cipher, foreign_key = "cipher_uuid")]
|
||||||
pub struct Attachment {
|
#[primary_key(id)]
|
||||||
pub id: String,
|
pub struct Attachment {
|
||||||
pub cipher_uuid: String,
|
pub id: String,
|
||||||
pub file_name: String,
|
pub cipher_uuid: String,
|
||||||
pub file_size: i32,
|
pub file_name: String,
|
||||||
pub akey: Option<String>,
|
pub file_size: i32,
|
||||||
|
pub akey: Option<String>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
|
@ -50,43 +52,46 @@ impl Attachment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::db::schema::{attachments, ciphers};
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl Attachment {
|
impl Attachment {
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
|
||||||
diesel::insert_into(attachments::table)
|
|
||||||
.values(self)
|
|
||||||
.on_conflict(attachments::id)
|
|
||||||
.do_update()
|
|
||||||
.set(self)
|
|
||||||
.execute(&**conn)
|
|
||||||
.map_res("Error saving attachment")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "postgresql"))]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::replace_into(attachments::table)
|
db_run! { conn:
|
||||||
.values(self)
|
sqlite, mysql {
|
||||||
.execute(&**conn)
|
diesel::replace_into(attachments::table)
|
||||||
.map_res("Error saving attachment")
|
.values(AttachmentDb::to_db(self))
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error saving attachment")
|
||||||
|
}
|
||||||
|
postgresql {
|
||||||
|
let value = AttachmentDb::to_db(self);
|
||||||
|
diesel::insert_into(attachments::table)
|
||||||
|
.values(&value)
|
||||||
|
.on_conflict(attachments::id)
|
||||||
|
.do_update()
|
||||||
|
.set(&value)
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error saving attachment")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
crate::util::retry(
|
db_run! { conn: {
|
||||||
|| diesel::delete(attachments::table.filter(attachments::id.eq(&self.id))).execute(&**conn),
|
crate::util::retry(
|
||||||
10,
|
|| diesel::delete(attachments::table.filter(attachments::id.eq(&self.id))).execute(conn),
|
||||||
)
|
10,
|
||||||
.map_res("Error deleting attachment")?;
|
)
|
||||||
|
.map_res("Error deleting attachment")?;
|
||||||
|
|
||||||
crate::util::delete_file(&self.get_file_path())?;
|
crate::util::delete_file(&self.get_file_path())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -97,67 +102,78 @@ impl Attachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_id(id: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_id(id: &str, conn: &DbConn) -> Option<Self> {
|
||||||
let id = id.to_lowercase();
|
db_run! { conn: {
|
||||||
|
attachments::table
|
||||||
attachments::table
|
.filter(attachments::id.eq(id.to_lowercase()))
|
||||||
.filter(attachments::id.eq(id))
|
.first::<AttachmentDb>(conn)
|
||||||
.first::<Self>(&**conn)
|
.ok()
|
||||||
.ok()
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_cipher(cipher_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_cipher(cipher_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
attachments::table
|
db_run! { conn: {
|
||||||
.filter(attachments::cipher_uuid.eq(cipher_uuid))
|
attachments::table
|
||||||
.load::<Self>(&**conn)
|
.filter(attachments::cipher_uuid.eq(cipher_uuid))
|
||||||
.expect("Error loading attachments")
|
.load::<AttachmentDb>(conn)
|
||||||
|
.expect("Error loading attachments")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_ciphers(cipher_uuids: Vec<String>, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_ciphers(cipher_uuids: Vec<String>, conn: &DbConn) -> Vec<Self> {
|
||||||
attachments::table
|
db_run! { conn: {
|
||||||
.filter(attachments::cipher_uuid.eq_any(cipher_uuids))
|
attachments::table
|
||||||
.load::<Self>(&**conn)
|
.filter(attachments::cipher_uuid.eq_any(cipher_uuids))
|
||||||
.expect("Error loading attachments")
|
.load::<AttachmentDb>(conn)
|
||||||
|
.expect("Error loading attachments")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
|
pub fn size_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
|
||||||
let result: Option<i64> = attachments::table
|
db_run! { conn: {
|
||||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
let result: Option<i64> = attachments::table
|
||||||
.filter(ciphers::user_uuid.eq(user_uuid))
|
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||||
.select(diesel::dsl::sum(attachments::file_size))
|
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||||
.first(&**conn)
|
.select(diesel::dsl::sum(attachments::file_size))
|
||||||
.expect("Error loading user attachment total size");
|
.first(conn)
|
||||||
|
.expect("Error loading user attachment total size");
|
||||||
result.unwrap_or(0)
|
result.unwrap_or(0)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
|
pub fn count_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
|
||||||
attachments::table
|
db_run! { conn: {
|
||||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
attachments::table
|
||||||
.filter(ciphers::user_uuid.eq(user_uuid))
|
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||||
.count()
|
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||||
.first::<i64>(&**conn)
|
.count()
|
||||||
.ok()
|
.first(conn)
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
|
pub fn size_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
|
||||||
let result: Option<i64> = attachments::table
|
db_run! { conn: {
|
||||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
let result: Option<i64> = attachments::table
|
||||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||||
.select(diesel::dsl::sum(attachments::file_size))
|
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||||
.first(&**conn)
|
.select(diesel::dsl::sum(attachments::file_size))
|
||||||
.expect("Error loading user attachment total size");
|
.first(conn)
|
||||||
|
.expect("Error loading user attachment total size");
|
||||||
result.unwrap_or(0)
|
result.unwrap_or(0)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
|
pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
|
||||||
attachments::table
|
db_run! { conn: {
|
||||||
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
attachments::table
|
||||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
|
||||||
.count()
|
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||||
.first(&**conn)
|
.count()
|
||||||
.ok()
|
.first(conn)
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,35 +5,37 @@ use super::{
|
||||||
Attachment, CollectionCipher, FolderCipher, Organization, User, UserOrgStatus, UserOrgType, UserOrganization,
|
Attachment, CollectionCipher, FolderCipher, Organization, User, UserOrgStatus, UserOrgType, UserOrganization,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
db_object! {
|
||||||
#[table_name = "ciphers"]
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
||||||
#[changeset_options(treat_none_as_null="true")]
|
#[table_name = "ciphers"]
|
||||||
#[belongs_to(User, foreign_key = "user_uuid")]
|
#[changeset_options(treat_none_as_null="true")]
|
||||||
#[belongs_to(Organization, foreign_key = "organization_uuid")]
|
#[belongs_to(User, foreign_key = "user_uuid")]
|
||||||
#[primary_key(uuid)]
|
#[belongs_to(Organization, foreign_key = "organization_uuid")]
|
||||||
pub struct Cipher {
|
#[primary_key(uuid)]
|
||||||
pub uuid: String,
|
pub struct Cipher {
|
||||||
pub created_at: NaiveDateTime,
|
pub uuid: String,
|
||||||
pub updated_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
|
pub updated_at: NaiveDateTime,
|
||||||
|
|
||||||
pub user_uuid: Option<String>,
|
pub user_uuid: Option<String>,
|
||||||
pub organization_uuid: Option<String>,
|
pub organization_uuid: Option<String>,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Login = 1,
|
Login = 1,
|
||||||
SecureNote = 2,
|
SecureNote = 2,
|
||||||
Card = 3,
|
Card = 3,
|
||||||
Identity = 4
|
Identity = 4
|
||||||
*/
|
*/
|
||||||
pub atype: i32,
|
pub atype: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub notes: Option<String>,
|
pub notes: Option<String>,
|
||||||
pub fields: Option<String>,
|
pub fields: Option<String>,
|
||||||
|
|
||||||
pub data: String,
|
pub data: String,
|
||||||
|
|
||||||
pub password_history: Option<String>,
|
pub password_history: Option<String>,
|
||||||
pub deleted_at: Option<NaiveDateTime>,
|
pub deleted_at: Option<NaiveDateTime>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
|
@ -62,9 +64,7 @@ impl Cipher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::db::schema::*;
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
@ -81,7 +81,7 @@ impl Cipher {
|
||||||
let password_history_json = self.password_history.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
|
let password_history_json = self.password_history.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
|
||||||
|
|
||||||
let (read_only, hide_passwords) =
|
let (read_only, hide_passwords) =
|
||||||
match self.get_access_restrictions(&user_uuid, &conn) {
|
match self.get_access_restrictions(&user_uuid, conn) {
|
||||||
Some((ro, hp)) => (ro, hp),
|
Some((ro, hp)) => (ro, hp),
|
||||||
None => {
|
None => {
|
||||||
error!("Cipher ownership assertion failure");
|
error!("Cipher ownership assertion failure");
|
||||||
|
@ -125,14 +125,14 @@ impl Cipher {
|
||||||
"Type": self.atype,
|
"Type": self.atype,
|
||||||
"RevisionDate": format_date(&self.updated_at),
|
"RevisionDate": format_date(&self.updated_at),
|
||||||
"DeletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(format_date(&d))),
|
"DeletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(format_date(&d))),
|
||||||
"FolderId": self.get_folder_uuid(&user_uuid, &conn),
|
"FolderId": self.get_folder_uuid(&user_uuid, conn),
|
||||||
"Favorite": self.is_favorite(&user_uuid, &conn),
|
"Favorite": self.is_favorite(&user_uuid, conn),
|
||||||
"OrganizationId": self.organization_uuid,
|
"OrganizationId": self.organization_uuid,
|
||||||
"Attachments": attachments_json,
|
"Attachments": attachments_json,
|
||||||
"OrganizationUseTotp": true,
|
"OrganizationUseTotp": true,
|
||||||
|
|
||||||
// This field is specific to the cipherDetails type.
|
// This field is specific to the cipherDetails type.
|
||||||
"CollectionIds": self.get_collections(user_uuid, &conn),
|
"CollectionIds": self.get_collections(user_uuid, conn),
|
||||||
|
|
||||||
"Name": self.name,
|
"Name": self.name,
|
||||||
"Notes": self.notes,
|
"Notes": self.notes,
|
||||||
|
@ -183,41 +183,42 @@ impl Cipher {
|
||||||
user_uuids
|
user_uuids
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||||
self.update_users_revision(conn);
|
self.update_users_revision(conn);
|
||||||
self.updated_at = Utc::now().naive_utc();
|
self.updated_at = Utc::now().naive_utc();
|
||||||
|
|
||||||
diesel::insert_into(ciphers::table)
|
db_run! { conn:
|
||||||
.values(&*self)
|
sqlite, mysql {
|
||||||
.on_conflict(ciphers::uuid)
|
diesel::replace_into(ciphers::table)
|
||||||
.do_update()
|
.values(CipherDb::to_db(self))
|
||||||
.set(&*self)
|
.execute(conn)
|
||||||
.execute(&**conn)
|
.map_res("Error saving cipher")
|
||||||
.map_res("Error saving cipher")
|
}
|
||||||
}
|
postgresql {
|
||||||
|
let value = CipherDb::to_db(self);
|
||||||
#[cfg(not(feature = "postgresql"))]
|
diesel::insert_into(ciphers::table)
|
||||||
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
.values(&value)
|
||||||
self.update_users_revision(conn);
|
.on_conflict(ciphers::uuid)
|
||||||
self.updated_at = Utc::now().naive_utc();
|
.do_update()
|
||||||
|
.set(&value)
|
||||||
diesel::replace_into(ciphers::table)
|
.execute(conn)
|
||||||
.values(&*self)
|
.map_res("Error saving cipher")
|
||||||
.execute(&**conn)
|
}
|
||||||
.map_res("Error saving cipher")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||||
self.update_users_revision(conn);
|
self.update_users_revision(conn);
|
||||||
|
|
||||||
FolderCipher::delete_all_by_cipher(&self.uuid, &conn)?;
|
FolderCipher::delete_all_by_cipher(&self.uuid, conn)?;
|
||||||
CollectionCipher::delete_all_by_cipher(&self.uuid, &conn)?;
|
CollectionCipher::delete_all_by_cipher(&self.uuid, conn)?;
|
||||||
Attachment::delete_all_by_cipher(&self.uuid, &conn)?;
|
Attachment::delete_all_by_cipher(&self.uuid, conn)?;
|
||||||
|
|
||||||
diesel::delete(ciphers::table.filter(ciphers::uuid.eq(&self.uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(ciphers::table.filter(ciphers::uuid.eq(&self.uuid)))
|
||||||
.map_res("Error deleting cipher")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting cipher")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -235,28 +236,28 @@ impl Cipher {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
User::update_uuid_revision(user_uuid, &conn);
|
User::update_uuid_revision(user_uuid, conn);
|
||||||
|
|
||||||
match (self.get_folder_uuid(&user_uuid, &conn), folder_uuid) {
|
match (self.get_folder_uuid(&user_uuid, conn), folder_uuid) {
|
||||||
// No changes
|
// No changes
|
||||||
(None, None) => Ok(()),
|
(None, None) => Ok(()),
|
||||||
(Some(ref old), Some(ref new)) if old == new => Ok(()),
|
(Some(ref old), Some(ref new)) if old == new => Ok(()),
|
||||||
|
|
||||||
// Add to folder
|
// Add to folder
|
||||||
(None, Some(new)) => FolderCipher::new(&new, &self.uuid).save(&conn),
|
(None, Some(new)) => FolderCipher::new(&new, &self.uuid).save(conn),
|
||||||
|
|
||||||
// Remove from folder
|
// Remove from folder
|
||||||
(Some(old), None) => match FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, &conn) {
|
(Some(old), None) => match FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn) {
|
||||||
Some(old) => old.delete(&conn),
|
Some(old) => old.delete(conn),
|
||||||
None => err!("Couldn't move from previous folder"),
|
None => err!("Couldn't move from previous folder"),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Move to another folder
|
// Move to another folder
|
||||||
(Some(old), Some(new)) => {
|
(Some(old), Some(new)) => {
|
||||||
if let Some(old) = FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, &conn) {
|
if let Some(old) = FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn) {
|
||||||
old.delete(&conn)?;
|
old.delete(conn)?;
|
||||||
}
|
}
|
||||||
FolderCipher::new(&new, &self.uuid).save(&conn)
|
FolderCipher::new(&new, &self.uuid).save(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,7 +270,7 @@ impl Cipher {
|
||||||
/// Returns whether this cipher is owned by an org in which the user has full access.
|
/// Returns whether this cipher is owned by an org in which the user has full access.
|
||||||
pub fn is_in_full_access_org(&self, user_uuid: &str, conn: &DbConn) -> bool {
|
pub fn is_in_full_access_org(&self, user_uuid: &str, conn: &DbConn) -> bool {
|
||||||
if let Some(ref org_uuid) = self.organization_uuid {
|
if let Some(ref org_uuid) = self.organization_uuid {
|
||||||
if let Some(user_org) = UserOrganization::find_by_user_and_org(&user_uuid, &org_uuid, &conn) {
|
if let Some(user_org) = UserOrganization::find_by_user_and_org(&user_uuid, &org_uuid, conn) {
|
||||||
return user_org.has_full_access();
|
return user_org.has_full_access();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,38 +291,40 @@ impl Cipher {
|
||||||
return Some((false, false));
|
return Some((false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether this cipher is in any collections accessible to the
|
db_run! {conn: {
|
||||||
// user. If so, retrieve the access flags for each collection.
|
// Check whether this cipher is in any collections accessible to the
|
||||||
let query = ciphers::table
|
// user. If so, retrieve the access flags for each collection.
|
||||||
.filter(ciphers::uuid.eq(&self.uuid))
|
let query = ciphers::table
|
||||||
.inner_join(ciphers_collections::table.on(
|
.filter(ciphers::uuid.eq(&self.uuid))
|
||||||
ciphers::uuid.eq(ciphers_collections::cipher_uuid)))
|
.inner_join(ciphers_collections::table.on(
|
||||||
.inner_join(users_collections::table.on(
|
ciphers::uuid.eq(ciphers_collections::cipher_uuid)))
|
||||||
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
.inner_join(users_collections::table.on(
|
||||||
.and(users_collections::user_uuid.eq(user_uuid))))
|
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
||||||
.select((users_collections::read_only, users_collections::hide_passwords));
|
.and(users_collections::user_uuid.eq(user_uuid))))
|
||||||
|
.select((users_collections::read_only, users_collections::hide_passwords));
|
||||||
|
|
||||||
// There's an edge case where a cipher can be in multiple collections
|
// There's an edge case where a cipher can be in multiple collections
|
||||||
// with inconsistent access flags. For example, a cipher could be in
|
// with inconsistent access flags. For example, a cipher could be in
|
||||||
// one collection where the user has read-only access, but also in
|
// one collection where the user has read-only access, but also in
|
||||||
// another collection where the user has read/write access. To handle
|
// another collection where the user has read/write access. To handle
|
||||||
// this, we do a boolean OR of all values in each of the `read_only`
|
// this, we do a boolean OR of all values in each of the `read_only`
|
||||||
// and `hide_passwords` columns. This could ideally be done as part
|
// and `hide_passwords` columns. This could ideally be done as part
|
||||||
// of the query, but Diesel doesn't support a max() or bool_or()
|
// of the query, but Diesel doesn't support a max() or bool_or()
|
||||||
// function on booleans and this behavior isn't portable anyway.
|
// function on booleans and this behavior isn't portable anyway.
|
||||||
if let Some(vec) = query.load::<(bool, bool)>(&**conn).ok() {
|
if let Some(vec) = query.load::<(bool, bool)>(conn).ok() {
|
||||||
let mut read_only = false;
|
let mut read_only = false;
|
||||||
let mut hide_passwords = false;
|
let mut hide_passwords = false;
|
||||||
for (ro, hp) in vec.iter() {
|
for (ro, hp) in vec.iter() {
|
||||||
read_only |= ro;
|
read_only |= ro;
|
||||||
hide_passwords |= hp;
|
hide_passwords |= hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((read_only, hide_passwords))
|
||||||
|
} else {
|
||||||
|
// This cipher isn't in any collections accessible to the user.
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
Some((read_only, hide_passwords))
|
|
||||||
} else {
|
|
||||||
// This cipher isn't in any collections accessible to the user.
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
|
pub fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
|
||||||
|
@ -337,12 +340,14 @@ impl Cipher {
|
||||||
|
|
||||||
// Returns whether this cipher is a favorite of the specified user.
|
// Returns whether this cipher is a favorite of the specified user.
|
||||||
pub fn is_favorite(&self, user_uuid: &str, conn: &DbConn) -> bool {
|
pub fn is_favorite(&self, user_uuid: &str, conn: &DbConn) -> bool {
|
||||||
let query = favorites::table
|
db_run!{ conn: {
|
||||||
.filter(favorites::user_uuid.eq(user_uuid))
|
let query = favorites::table
|
||||||
.filter(favorites::cipher_uuid.eq(&self.uuid))
|
.filter(favorites::user_uuid.eq(user_uuid))
|
||||||
.count();
|
.filter(favorites::cipher_uuid.eq(&self.uuid))
|
||||||
|
.count();
|
||||||
|
|
||||||
query.first::<i64>(&**conn).ok().unwrap_or(0) != 0
|
query.first::<i64>(conn).ok().unwrap_or(0) != 0
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates whether this cipher is a favorite of the specified user.
|
// Updates whether this cipher is a favorite of the specified user.
|
||||||
|
@ -356,23 +361,27 @@ impl Cipher {
|
||||||
match (old, new) {
|
match (old, new) {
|
||||||
(false, true) => {
|
(false, true) => {
|
||||||
User::update_uuid_revision(user_uuid, &conn);
|
User::update_uuid_revision(user_uuid, &conn);
|
||||||
diesel::insert_into(favorites::table)
|
db_run!{ conn: {
|
||||||
.values((
|
diesel::insert_into(favorites::table)
|
||||||
favorites::user_uuid.eq(user_uuid),
|
.values((
|
||||||
favorites::cipher_uuid.eq(&self.uuid),
|
favorites::user_uuid.eq(user_uuid),
|
||||||
))
|
favorites::cipher_uuid.eq(&self.uuid),
|
||||||
.execute(&**conn)
|
))
|
||||||
.map_res("Error adding favorite")
|
.execute(conn)
|
||||||
|
.map_res("Error adding favorite")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
(true, false) => {
|
(true, false) => {
|
||||||
User::update_uuid_revision(user_uuid, &conn);
|
User::update_uuid_revision(user_uuid, &conn);
|
||||||
diesel::delete(
|
db_run!{ conn: {
|
||||||
favorites::table
|
diesel::delete(
|
||||||
.filter(favorites::user_uuid.eq(user_uuid))
|
favorites::table
|
||||||
.filter(favorites::cipher_uuid.eq(&self.uuid))
|
.filter(favorites::user_uuid.eq(user_uuid))
|
||||||
)
|
.filter(favorites::cipher_uuid.eq(&self.uuid))
|
||||||
.execute(&**conn)
|
)
|
||||||
.map_res("Error removing favorite")
|
.execute(conn)
|
||||||
|
.map_res("Error removing favorite")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
// Otherwise, the favorite status is already what it should be.
|
// Otherwise, the favorite status is already what it should be.
|
||||||
_ => Ok(())
|
_ => Ok(())
|
||||||
|
@ -380,112 +389,131 @@ impl Cipher {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_folder_uuid(&self, user_uuid: &str, conn: &DbConn) -> Option<String> {
|
pub fn get_folder_uuid(&self, user_uuid: &str, conn: &DbConn) -> Option<String> {
|
||||||
folders_ciphers::table
|
db_run! {conn: {
|
||||||
.inner_join(folders::table)
|
folders_ciphers::table
|
||||||
.filter(folders::user_uuid.eq(&user_uuid))
|
.inner_join(folders::table)
|
||||||
.filter(folders_ciphers::cipher_uuid.eq(&self.uuid))
|
.filter(folders::user_uuid.eq(&user_uuid))
|
||||||
.select(folders_ciphers::folder_uuid)
|
.filter(folders_ciphers::cipher_uuid.eq(&self.uuid))
|
||||||
.first::<String>(&**conn)
|
.select(folders_ciphers::folder_uuid)
|
||||||
.ok()
|
.first::<String>(conn)
|
||||||
|
.ok()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
ciphers::table
|
db_run! {conn: {
|
||||||
.filter(ciphers::uuid.eq(uuid))
|
ciphers::table
|
||||||
.first::<Self>(&**conn)
|
.filter(ciphers::uuid.eq(uuid))
|
||||||
.ok()
|
.first::<CipherDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all ciphers accessible to user
|
// Find all ciphers accessible to user
|
||||||
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
ciphers::table
|
db_run! {conn: {
|
||||||
.left_join(users_organizations::table.on(
|
ciphers::table
|
||||||
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()).and(
|
.left_join(users_organizations::table.on(
|
||||||
users_organizations::user_uuid.eq(user_uuid).and(
|
ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()).and(
|
||||||
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
users_organizations::user_uuid.eq(user_uuid).and(
|
||||||
)
|
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
||||||
)
|
)
|
||||||
))
|
|
||||||
.left_join(ciphers_collections::table.on(
|
|
||||||
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
|
||||||
))
|
|
||||||
.left_join(users_collections::table.on(
|
|
||||||
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
|
||||||
))
|
|
||||||
.filter(ciphers::user_uuid.eq(user_uuid).or( // Cipher owner
|
|
||||||
users_organizations::access_all.eq(true).or( // access_all in Organization
|
|
||||||
users_organizations::atype.le(UserOrgType::Admin as i32).or( // Org admin or owner
|
|
||||||
users_collections::user_uuid.eq(user_uuid).and( // Access to Collection
|
|
||||||
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
)
|
.left_join(ciphers_collections::table.on(
|
||||||
))
|
ciphers::uuid.eq(ciphers_collections::cipher_uuid)
|
||||||
.select(ciphers::all_columns)
|
))
|
||||||
.distinct()
|
.left_join(users_collections::table.on(
|
||||||
.load::<Self>(&**conn).expect("Error loading ciphers")
|
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid)
|
||||||
|
))
|
||||||
|
.filter(ciphers::user_uuid.eq(user_uuid).or( // Cipher owner
|
||||||
|
users_organizations::access_all.eq(true).or( // access_all in Organization
|
||||||
|
users_organizations::atype.le(UserOrgType::Admin as i32).or( // Org admin or owner
|
||||||
|
users_collections::user_uuid.eq(user_uuid).and( // Access to Collection
|
||||||
|
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.select(ciphers::all_columns)
|
||||||
|
.distinct()
|
||||||
|
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all ciphers directly owned by user
|
// Find all ciphers directly owned by user
|
||||||
pub fn find_owned_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_owned_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
ciphers::table
|
db_run! {conn: {
|
||||||
.filter(ciphers::user_uuid.eq(user_uuid))
|
ciphers::table
|
||||||
.load::<Self>(&**conn).expect("Error loading ciphers")
|
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||||
|
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
|
pub fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
|
||||||
ciphers::table
|
db_run! {conn: {
|
||||||
.filter(ciphers::user_uuid.eq(user_uuid))
|
ciphers::table
|
||||||
.count()
|
.filter(ciphers::user_uuid.eq(user_uuid))
|
||||||
.first::<i64>(&**conn)
|
.count()
|
||||||
.ok()
|
.first::<i64>(conn)
|
||||||
.unwrap_or(0)
|
.ok()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
ciphers::table
|
db_run! {conn: {
|
||||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
ciphers::table
|
||||||
.load::<Self>(&**conn).expect("Error loading ciphers")
|
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||||
|
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
|
pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
|
||||||
ciphers::table
|
db_run! {conn: {
|
||||||
.filter(ciphers::organization_uuid.eq(org_uuid))
|
ciphers::table
|
||||||
.count()
|
.filter(ciphers::organization_uuid.eq(org_uuid))
|
||||||
.first::<i64>(&**conn)
|
.count()
|
||||||
.ok()
|
.first::<i64>(conn)
|
||||||
.unwrap_or(0)
|
.ok()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
folders_ciphers::table.inner_join(ciphers::table)
|
db_run! {conn: {
|
||||||
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
folders_ciphers::table.inner_join(ciphers::table)
|
||||||
.select(ciphers::all_columns)
|
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
||||||
.load::<Self>(&**conn).expect("Error loading ciphers")
|
.select(ciphers::all_columns)
|
||||||
|
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_collections(&self, user_id: &str, conn: &DbConn) -> Vec<String> {
|
pub fn get_collections(&self, user_id: &str, conn: &DbConn) -> Vec<String> {
|
||||||
ciphers_collections::table
|
db_run! {conn: {
|
||||||
.inner_join(collections::table.on(
|
ciphers_collections::table
|
||||||
collections::uuid.eq(ciphers_collections::collection_uuid)
|
.inner_join(collections::table.on(
|
||||||
))
|
collections::uuid.eq(ciphers_collections::collection_uuid)
|
||||||
.inner_join(users_organizations::table.on(
|
))
|
||||||
users_organizations::org_uuid.eq(collections::org_uuid).and(
|
.inner_join(users_organizations::table.on(
|
||||||
users_organizations::user_uuid.eq(user_id)
|
users_organizations::org_uuid.eq(collections::org_uuid).and(
|
||||||
)
|
users_organizations::user_uuid.eq(user_id)
|
||||||
))
|
)
|
||||||
.left_join(users_collections::table.on(
|
))
|
||||||
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid).and(
|
.left_join(users_collections::table.on(
|
||||||
users_collections::user_uuid.eq(user_id)
|
users_collections::collection_uuid.eq(ciphers_collections::collection_uuid).and(
|
||||||
)
|
users_collections::user_uuid.eq(user_id)
|
||||||
))
|
)
|
||||||
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
))
|
||||||
.filter(users_collections::user_uuid.eq(user_id).or( // User has access to collection
|
.filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
|
||||||
users_organizations::access_all.eq(true).or( // User has access all
|
.filter(users_collections::user_uuid.eq(user_id).or( // User has access to collection
|
||||||
users_organizations::atype.le(UserOrgType::Admin as i32) // User is admin or owner
|
users_organizations::access_all.eq(true).or( // User has access all
|
||||||
)
|
users_organizations::atype.le(UserOrgType::Admin as i32) // User is admin or owner
|
||||||
))
|
)
|
||||||
.select(ciphers_collections::collection_uuid)
|
))
|
||||||
.load::<String>(&**conn).unwrap_or_default()
|
.select(ciphers_collections::collection_uuid)
|
||||||
|
.load::<String>(conn).unwrap_or_default()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,39 @@
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
|
use super::{Organization, UserOrgStatus, UserOrgType, UserOrganization, User, Cipher};
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
db_object! {
|
||||||
#[table_name = "collections"]
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
||||||
#[belongs_to(Organization, foreign_key = "org_uuid")]
|
#[table_name = "collections"]
|
||||||
#[primary_key(uuid)]
|
#[belongs_to(Organization, foreign_key = "org_uuid")]
|
||||||
pub struct Collection {
|
#[primary_key(uuid)]
|
||||||
pub uuid: String,
|
pub struct Collection {
|
||||||
pub org_uuid: String,
|
pub uuid: String,
|
||||||
pub name: String,
|
pub org_uuid: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
|
||||||
|
#[table_name = "users_collections"]
|
||||||
|
#[belongs_to(User, foreign_key = "user_uuid")]
|
||||||
|
#[belongs_to(Collection, foreign_key = "collection_uuid")]
|
||||||
|
#[primary_key(user_uuid, collection_uuid)]
|
||||||
|
pub struct CollectionUser {
|
||||||
|
pub user_uuid: String,
|
||||||
|
pub collection_uuid: String,
|
||||||
|
pub read_only: bool,
|
||||||
|
pub hide_passwords: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
|
||||||
|
#[table_name = "ciphers_collections"]
|
||||||
|
#[belongs_to(Cipher, foreign_key = "cipher_uuid")]
|
||||||
|
#[belongs_to(Collection, foreign_key = "collection_uuid")]
|
||||||
|
#[primary_key(cipher_uuid, collection_uuid)]
|
||||||
|
pub struct CollectionCipher {
|
||||||
|
pub cipher_uuid: String,
|
||||||
|
pub collection_uuid: String,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
|
@ -33,36 +57,34 @@ impl Collection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::db::schema::*;
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl Collection {
|
impl Collection {
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
self.update_users_revision(conn);
|
self.update_users_revision(conn);
|
||||||
|
|
||||||
diesel::insert_into(collections::table)
|
db_run! { conn:
|
||||||
.values(self)
|
sqlite, mysql {
|
||||||
.on_conflict(collections::uuid)
|
diesel::replace_into(collections::table)
|
||||||
.do_update()
|
.values(CollectionDb::to_db(self))
|
||||||
.set(self)
|
.execute(conn)
|
||||||
.execute(&**conn)
|
.map_res("Error saving collection")
|
||||||
.map_res("Error saving collection")
|
}
|
||||||
}
|
postgresql {
|
||||||
|
let value = CollectionDb::to_db(self);
|
||||||
#[cfg(not(feature = "postgresql"))]
|
diesel::insert_into(collections::table)
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
.values(&value)
|
||||||
self.update_users_revision(conn);
|
.on_conflict(collections::uuid)
|
||||||
|
.do_update()
|
||||||
diesel::replace_into(collections::table)
|
.set(&value)
|
||||||
.values(self)
|
.execute(conn)
|
||||||
.execute(&**conn)
|
.map_res("Error saving collection")
|
||||||
.map_res("Error saving collection")
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -70,9 +92,11 @@ impl Collection {
|
||||||
CollectionCipher::delete_all_by_collection(&self.uuid, &conn)?;
|
CollectionCipher::delete_all_by_collection(&self.uuid, &conn)?;
|
||||||
CollectionUser::delete_all_by_collection(&self.uuid, &conn)?;
|
CollectionUser::delete_all_by_collection(&self.uuid, &conn)?;
|
||||||
|
|
||||||
diesel::delete(collections::table.filter(collections::uuid.eq(self.uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(collections::table.filter(collections::uuid.eq(self.uuid)))
|
||||||
.map_res("Error deleting collection")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting collection")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -91,33 +115,38 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
collections::table
|
db_run! { conn: {
|
||||||
.filter(collections::uuid.eq(uuid))
|
collections::table
|
||||||
.first::<Self>(&**conn)
|
.filter(collections::uuid.eq(uuid))
|
||||||
.ok()
|
.first::<CollectionDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_user_uuid(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user_uuid(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
collections::table
|
db_run! { conn: {
|
||||||
.left_join(users_collections::table.on(
|
collections::table
|
||||||
users_collections::collection_uuid.eq(collections::uuid).and(
|
.left_join(users_collections::table.on(
|
||||||
users_collections::user_uuid.eq(user_uuid)
|
users_collections::collection_uuid.eq(collections::uuid).and(
|
||||||
|
users_collections::user_uuid.eq(user_uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.left_join(users_organizations::table.on(
|
||||||
|
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
||||||
|
users_organizations::user_uuid.eq(user_uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.filter(
|
||||||
|
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
||||||
)
|
)
|
||||||
))
|
.filter(
|
||||||
.left_join(users_organizations::table.on(
|
users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection
|
||||||
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
users_organizations::access_all.eq(true) // access_all in Organization
|
||||||
users_organizations::user_uuid.eq(user_uuid)
|
)
|
||||||
)
|
).select(collections::all_columns)
|
||||||
))
|
.load::<CollectionDb>(conn).expect("Error loading collections").from_db()
|
||||||
.filter(
|
}}
|
||||||
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection
|
|
||||||
users_organizations::access_all.eq(true) // access_all in Organization
|
|
||||||
)
|
|
||||||
).select(collections::all_columns)
|
|
||||||
.load::<Self>(&**conn).expect("Error loading collections")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
|
@ -128,42 +157,51 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
collections::table
|
db_run! { conn: {
|
||||||
.filter(collections::org_uuid.eq(org_uuid))
|
collections::table
|
||||||
.load::<Self>(&**conn)
|
.filter(collections::org_uuid.eq(org_uuid))
|
||||||
.expect("Error loading collections")
|
.load::<CollectionDb>(conn)
|
||||||
|
.expect("Error loading collections")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
collections::table
|
db_run! { conn: {
|
||||||
.filter(collections::uuid.eq(uuid))
|
collections::table
|
||||||
.filter(collections::org_uuid.eq(org_uuid))
|
.filter(collections::uuid.eq(uuid))
|
||||||
.select(collections::all_columns)
|
.filter(collections::org_uuid.eq(org_uuid))
|
||||||
.first::<Self>(&**conn)
|
.select(collections::all_columns)
|
||||||
.ok()
|
.first::<CollectionDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
collections::table
|
db_run! { conn: {
|
||||||
.left_join(users_collections::table.on(
|
collections::table
|
||||||
users_collections::collection_uuid.eq(collections::uuid).and(
|
.left_join(users_collections::table.on(
|
||||||
users_collections::user_uuid.eq(user_uuid)
|
users_collections::collection_uuid.eq(collections::uuid).and(
|
||||||
)
|
users_collections::user_uuid.eq(user_uuid)
|
||||||
))
|
|
||||||
.left_join(users_organizations::table.on(
|
|
||||||
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
|
||||||
users_organizations::user_uuid.eq(user_uuid)
|
|
||||||
)
|
|
||||||
))
|
|
||||||
.filter(collections::uuid.eq(uuid))
|
|
||||||
.filter(
|
|
||||||
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
|
|
||||||
users_organizations::access_all.eq(true).or( // access_all in Organization
|
|
||||||
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
|
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
).select(collections::all_columns)
|
.left_join(users_organizations::table.on(
|
||||||
.first::<Self>(&**conn).ok()
|
collections::org_uuid.eq(users_organizations::org_uuid).and(
|
||||||
|
users_organizations::user_uuid.eq(user_uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.filter(collections::uuid.eq(uuid))
|
||||||
|
.filter(
|
||||||
|
users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection
|
||||||
|
users_organizations::access_all.eq(true).or( // access_all in Organization
|
||||||
|
users_organizations::atype.le(UserOrgType::Admin as i32) // Org admin or owner
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).select(collections::all_columns)
|
||||||
|
.first::<CollectionDb>(conn).ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_writable_by_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
|
pub fn is_writable_by_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
|
||||||
|
@ -173,110 +211,108 @@ impl Collection {
|
||||||
if user_org.access_all {
|
if user_org.access_all {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
users_collections::table
|
db_run! { conn: {
|
||||||
.inner_join(collections::table)
|
users_collections::table
|
||||||
.filter(users_collections::collection_uuid.eq(&self.uuid))
|
.inner_join(collections::table)
|
||||||
.filter(users_collections::user_uuid.eq(&user_uuid))
|
.filter(users_collections::collection_uuid.eq(&self.uuid))
|
||||||
.filter(users_collections::read_only.eq(false))
|
.filter(users_collections::user_uuid.eq(&user_uuid))
|
||||||
.select(collections::all_columns)
|
.filter(users_collections::read_only.eq(false))
|
||||||
.first::<Self>(&**conn)
|
.select(collections::all_columns)
|
||||||
.ok()
|
.first::<CollectionDb>(conn)
|
||||||
.is_some() // Read only or no access to collection
|
.ok()
|
||||||
|
.is_some() // Read only or no access to collection
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::User;
|
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
|
|
||||||
#[table_name = "users_collections"]
|
|
||||||
#[belongs_to(User, foreign_key = "user_uuid")]
|
|
||||||
#[belongs_to(Collection, foreign_key = "collection_uuid")]
|
|
||||||
#[primary_key(user_uuid, collection_uuid)]
|
|
||||||
pub struct CollectionUser {
|
|
||||||
pub user_uuid: String,
|
|
||||||
pub collection_uuid: String,
|
|
||||||
pub read_only: bool,
|
|
||||||
pub hide_passwords: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl CollectionUser {
|
impl CollectionUser {
|
||||||
pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
users_collections::table
|
db_run! { conn: {
|
||||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
users_collections::table
|
||||||
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
|
.filter(users_collections::user_uuid.eq(user_uuid))
|
||||||
.filter(collections::org_uuid.eq(org_uuid))
|
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
|
||||||
.select(users_collections::all_columns)
|
.filter(collections::org_uuid.eq(org_uuid))
|
||||||
.load::<Self>(&**conn)
|
.select(users_collections::all_columns)
|
||||||
.expect("Error loading users_collections")
|
.load::<CollectionUserDb>(conn)
|
||||||
|
.expect("Error loading users_collections")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(user_uuid: &str, collection_uuid: &str, read_only: bool, hide_passwords: bool, conn: &DbConn) -> EmptyResult {
|
pub fn save(user_uuid: &str, collection_uuid: &str, read_only: bool, hide_passwords: bool, conn: &DbConn) -> EmptyResult {
|
||||||
User::update_uuid_revision(&user_uuid, conn);
|
User::update_uuid_revision(&user_uuid, conn);
|
||||||
|
|
||||||
diesel::insert_into(users_collections::table)
|
db_run! { conn:
|
||||||
.values((
|
sqlite, mysql {
|
||||||
users_collections::user_uuid.eq(user_uuid),
|
diesel::replace_into(users_collections::table)
|
||||||
users_collections::collection_uuid.eq(collection_uuid),
|
.values((
|
||||||
users_collections::read_only.eq(read_only),
|
users_collections::user_uuid.eq(user_uuid),
|
||||||
users_collections::hide_passwords.eq(hide_passwords),
|
users_collections::collection_uuid.eq(collection_uuid),
|
||||||
))
|
users_collections::read_only.eq(read_only),
|
||||||
.on_conflict((users_collections::user_uuid, users_collections::collection_uuid))
|
users_collections::hide_passwords.eq(hide_passwords),
|
||||||
.do_update()
|
))
|
||||||
.set((
|
.execute(conn)
|
||||||
users_collections::read_only.eq(read_only),
|
.map_res("Error adding user to collection")
|
||||||
users_collections::hide_passwords.eq(hide_passwords),
|
}
|
||||||
))
|
postgresql {
|
||||||
.execute(&**conn)
|
diesel::insert_into(users_collections::table)
|
||||||
.map_res("Error adding user to collection")
|
.values((
|
||||||
}
|
users_collections::user_uuid.eq(user_uuid),
|
||||||
|
users_collections::collection_uuid.eq(collection_uuid),
|
||||||
#[cfg(not(feature = "postgresql"))]
|
users_collections::read_only.eq(read_only),
|
||||||
pub fn save(user_uuid: &str, collection_uuid: &str, read_only: bool, hide_passwords: bool, conn: &DbConn) -> EmptyResult {
|
users_collections::hide_passwords.eq(hide_passwords),
|
||||||
User::update_uuid_revision(&user_uuid, conn);
|
))
|
||||||
|
.on_conflict((users_collections::user_uuid, users_collections::collection_uuid))
|
||||||
diesel::replace_into(users_collections::table)
|
.do_update()
|
||||||
.values((
|
.set((
|
||||||
users_collections::user_uuid.eq(user_uuid),
|
users_collections::read_only.eq(read_only),
|
||||||
users_collections::collection_uuid.eq(collection_uuid),
|
users_collections::hide_passwords.eq(hide_passwords),
|
||||||
users_collections::read_only.eq(read_only),
|
))
|
||||||
users_collections::hide_passwords.eq(hide_passwords),
|
.execute(conn)
|
||||||
))
|
.map_res("Error adding user to collection")
|
||||||
.execute(&**conn)
|
}
|
||||||
.map_res("Error adding user to collection")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
User::update_uuid_revision(&self.user_uuid, conn);
|
User::update_uuid_revision(&self.user_uuid, conn);
|
||||||
|
|
||||||
diesel::delete(
|
db_run! { conn: {
|
||||||
users_collections::table
|
diesel::delete(
|
||||||
.filter(users_collections::user_uuid.eq(&self.user_uuid))
|
users_collections::table
|
||||||
.filter(users_collections::collection_uuid.eq(&self.collection_uuid)),
|
.filter(users_collections::user_uuid.eq(&self.user_uuid))
|
||||||
)
|
.filter(users_collections::collection_uuid.eq(&self.collection_uuid)),
|
||||||
.execute(&**conn)
|
)
|
||||||
.map_res("Error removing user from collection")
|
.execute(conn)
|
||||||
|
.map_res("Error removing user from collection")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
users_collections::table
|
db_run! { conn: {
|
||||||
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
users_collections::table
|
||||||
.select(users_collections::all_columns)
|
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
||||||
.load::<Self>(&**conn)
|
.select(users_collections::all_columns)
|
||||||
.expect("Error loading users_collections")
|
.load::<CollectionUserDb>(conn)
|
||||||
|
.expect("Error loading users_collections")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_collection_and_user(collection_uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_collection_and_user(collection_uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
users_collections::table
|
db_run! { conn: {
|
||||||
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
users_collections::table
|
||||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
.filter(users_collections::collection_uuid.eq(collection_uuid))
|
||||||
.select(users_collections::all_columns)
|
.filter(users_collections::user_uuid.eq(user_uuid))
|
||||||
.first::<Self>(&**conn)
|
.select(users_collections::all_columns)
|
||||||
.ok()
|
.first::<CollectionUserDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -286,81 +322,81 @@ impl CollectionUser {
|
||||||
User::update_uuid_revision(&collection.user_uuid, conn);
|
User::update_uuid_revision(&collection.user_uuid, conn);
|
||||||
});
|
});
|
||||||
|
|
||||||
diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid)))
|
||||||
.map_res("Error deleting users from collection")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting users from collection")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
User::update_uuid_revision(&user_uuid, conn);
|
User::update_uuid_revision(&user_uuid, conn);
|
||||||
|
|
||||||
diesel::delete(users_collections::table.filter(users_collections::user_uuid.eq(user_uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(users_collections::table.filter(users_collections::user_uuid.eq(user_uuid)))
|
||||||
.map_res("Error removing user from collections")
|
.execute(conn)
|
||||||
|
.map_res("Error removing user from collections")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::Cipher;
|
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
|
|
||||||
#[table_name = "ciphers_collections"]
|
|
||||||
#[belongs_to(Cipher, foreign_key = "cipher_uuid")]
|
|
||||||
#[belongs_to(Collection, foreign_key = "collection_uuid")]
|
|
||||||
#[primary_key(cipher_uuid, collection_uuid)]
|
|
||||||
pub struct CollectionCipher {
|
|
||||||
pub cipher_uuid: String,
|
|
||||||
pub collection_uuid: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl CollectionCipher {
|
impl CollectionCipher {
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
Self::update_users_revision(&collection_uuid, conn);
|
Self::update_users_revision(&collection_uuid, conn);
|
||||||
diesel::insert_into(ciphers_collections::table)
|
|
||||||
.values((
|
|
||||||
ciphers_collections::cipher_uuid.eq(cipher_uuid),
|
|
||||||
ciphers_collections::collection_uuid.eq(collection_uuid),
|
|
||||||
))
|
|
||||||
.on_conflict((ciphers_collections::cipher_uuid, ciphers_collections::collection_uuid))
|
|
||||||
.do_nothing()
|
|
||||||
.execute(&**conn)
|
|
||||||
.map_res("Error adding cipher to collection")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "postgresql"))]
|
db_run! { conn:
|
||||||
pub fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
sqlite, mysql {
|
||||||
Self::update_users_revision(&collection_uuid, conn);
|
diesel::replace_into(ciphers_collections::table)
|
||||||
diesel::replace_into(ciphers_collections::table)
|
.values((
|
||||||
.values((
|
ciphers_collections::cipher_uuid.eq(cipher_uuid),
|
||||||
ciphers_collections::cipher_uuid.eq(cipher_uuid),
|
ciphers_collections::collection_uuid.eq(collection_uuid),
|
||||||
ciphers_collections::collection_uuid.eq(collection_uuid),
|
))
|
||||||
))
|
.execute(conn)
|
||||||
.execute(&**conn)
|
.map_res("Error adding cipher to collection")
|
||||||
.map_res("Error adding cipher to collection")
|
}
|
||||||
|
postgresql {
|
||||||
|
diesel::insert_into(ciphers_collections::table)
|
||||||
|
.values((
|
||||||
|
ciphers_collections::cipher_uuid.eq(cipher_uuid),
|
||||||
|
ciphers_collections::collection_uuid.eq(collection_uuid),
|
||||||
|
))
|
||||||
|
.on_conflict((ciphers_collections::cipher_uuid, ciphers_collections::collection_uuid))
|
||||||
|
.do_nothing()
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error adding cipher to collection")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
Self::update_users_revision(&collection_uuid, conn);
|
Self::update_users_revision(&collection_uuid, conn);
|
||||||
diesel::delete(
|
|
||||||
ciphers_collections::table
|
db_run! { conn: {
|
||||||
.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))
|
diesel::delete(
|
||||||
.filter(ciphers_collections::collection_uuid.eq(collection_uuid)),
|
ciphers_collections::table
|
||||||
)
|
.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))
|
||||||
.execute(&**conn)
|
.filter(ciphers_collections::collection_uuid.eq(collection_uuid)),
|
||||||
.map_res("Error deleting cipher from collection")
|
)
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error deleting cipher from collection")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)))
|
||||||
.map_res("Error removing cipher from collections")
|
.execute(conn)
|
||||||
|
.map_res("Error removing cipher from collections")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid)))
|
||||||
.map_res("Error removing ciphers from collection")
|
.execute(conn)
|
||||||
|
.map_res("Error removing ciphers from collection")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_users_revision(collection_uuid: &str, conn: &DbConn) {
|
pub fn update_users_revision(collection_uuid: &str, conn: &DbConn) {
|
||||||
|
|
|
@ -3,26 +3,28 @@ use chrono::{NaiveDateTime, Utc};
|
||||||
use super::User;
|
use super::User;
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
db_object! {
|
||||||
#[table_name = "devices"]
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
||||||
#[changeset_options(treat_none_as_null="true")]
|
#[table_name = "devices"]
|
||||||
#[belongs_to(User, foreign_key = "user_uuid")]
|
#[changeset_options(treat_none_as_null="true")]
|
||||||
#[primary_key(uuid)]
|
#[belongs_to(User, foreign_key = "user_uuid")]
|
||||||
pub struct Device {
|
#[primary_key(uuid)]
|
||||||
pub uuid: String,
|
pub struct Device {
|
||||||
pub created_at: NaiveDateTime,
|
pub uuid: String,
|
||||||
pub updated_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
|
pub updated_at: NaiveDateTime,
|
||||||
|
|
||||||
pub user_uuid: String,
|
pub user_uuid: String,
|
||||||
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// https://github.com/bitwarden/core/tree/master/src/Core/Enums
|
// https://github.com/bitwarden/core/tree/master/src/Core/Enums
|
||||||
pub atype: i32,
|
pub atype: i32,
|
||||||
pub push_token: Option<String>,
|
pub push_token: Option<String>,
|
||||||
|
|
||||||
pub refresh_token: String,
|
pub refresh_token: String,
|
||||||
|
|
||||||
pub twofactor_remember: Option<String>,
|
pub twofactor_remember: Option<String>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
|
@ -105,41 +107,39 @@ impl Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::db::schema::devices;
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl Device {
|
impl Device {
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||||
self.updated_at = Utc::now().naive_utc();
|
self.updated_at = Utc::now().naive_utc();
|
||||||
|
|
||||||
crate::util::retry(
|
db_run! { conn:
|
||||||
|| diesel::insert_into(devices::table).values(&*self).on_conflict(devices::uuid).do_update().set(&*self).execute(&**conn),
|
sqlite, mysql {
|
||||||
10,
|
crate::util::retry(
|
||||||
)
|
|| diesel::replace_into(devices::table).values(DeviceDb::to_db(self)).execute(conn),
|
||||||
.map_res("Error saving device")
|
10,
|
||||||
}
|
).map_res("Error saving device")
|
||||||
|
}
|
||||||
#[cfg(not(feature = "postgresql"))]
|
postgresql {
|
||||||
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
let value = DeviceDb::to_db(self);
|
||||||
self.updated_at = Utc::now().naive_utc();
|
crate::util::retry(
|
||||||
|
|| diesel::insert_into(devices::table).values(&value).on_conflict(devices::uuid).do_update().set(&value).execute(conn),
|
||||||
crate::util::retry(
|
10,
|
||||||
|| diesel::replace_into(devices::table).values(&*self).execute(&**conn),
|
).map_res("Error saving device")
|
||||||
10,
|
}
|
||||||
)
|
}
|
||||||
.map_res("Error saving device")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(devices::table.filter(devices::uuid.eq(self.uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(devices::table.filter(devices::uuid.eq(self.uuid)))
|
||||||
.map_res("Error removing device")
|
.execute(conn)
|
||||||
|
.map_res("Error removing device")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -150,23 +150,32 @@ impl Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
devices::table
|
db_run! { conn: {
|
||||||
.filter(devices::uuid.eq(uuid))
|
devices::table
|
||||||
.first::<Self>(&**conn)
|
.filter(devices::uuid.eq(uuid))
|
||||||
.ok()
|
.first::<DeviceDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_refresh_token(refresh_token: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_refresh_token(refresh_token: &str, conn: &DbConn) -> Option<Self> {
|
||||||
devices::table
|
db_run! { conn: {
|
||||||
.filter(devices::refresh_token.eq(refresh_token))
|
devices::table
|
||||||
.first::<Self>(&**conn)
|
.filter(devices::refresh_token.eq(refresh_token))
|
||||||
.ok()
|
.first::<DeviceDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
devices::table
|
db_run! { conn: {
|
||||||
.filter(devices::user_uuid.eq(user_uuid))
|
devices::table
|
||||||
.load::<Self>(&**conn)
|
.filter(devices::user_uuid.eq(user_uuid))
|
||||||
.expect("Error loading devices")
|
.load::<DeviceDb>(conn)
|
||||||
|
.expect("Error loading devices")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,28 @@ use serde_json::Value;
|
||||||
|
|
||||||
use super::{Cipher, User};
|
use super::{Cipher, User};
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
db_object! {
|
||||||
#[table_name = "folders"]
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
||||||
#[belongs_to(User, foreign_key = "user_uuid")]
|
#[table_name = "folders"]
|
||||||
#[primary_key(uuid)]
|
#[belongs_to(User, foreign_key = "user_uuid")]
|
||||||
pub struct Folder {
|
#[primary_key(uuid)]
|
||||||
pub uuid: String,
|
pub struct Folder {
|
||||||
pub created_at: NaiveDateTime,
|
pub uuid: String,
|
||||||
pub updated_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub user_uuid: String,
|
pub updated_at: NaiveDateTime,
|
||||||
pub name: String,
|
pub user_uuid: String,
|
||||||
}
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
|
||||||
#[table_name = "folders_ciphers"]
|
#[table_name = "folders_ciphers"]
|
||||||
#[belongs_to(Cipher, foreign_key = "cipher_uuid")]
|
#[belongs_to(Cipher, foreign_key = "cipher_uuid")]
|
||||||
#[belongs_to(Folder, foreign_key = "folder_uuid")]
|
#[belongs_to(Folder, foreign_key = "folder_uuid")]
|
||||||
#[primary_key(cipher_uuid, folder_uuid)]
|
#[primary_key(cipher_uuid, folder_uuid)]
|
||||||
pub struct FolderCipher {
|
pub struct FolderCipher {
|
||||||
pub cipher_uuid: String,
|
pub cipher_uuid: String,
|
||||||
pub folder_uuid: String,
|
pub folder_uuid: String,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
|
@ -61,47 +63,47 @@ impl FolderCipher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::db::schema::{folders, folders_ciphers};
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl Folder {
|
impl Folder {
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||||
User::update_uuid_revision(&self.user_uuid, conn);
|
User::update_uuid_revision(&self.user_uuid, conn);
|
||||||
self.updated_at = Utc::now().naive_utc();
|
self.updated_at = Utc::now().naive_utc();
|
||||||
|
|
||||||
diesel::insert_into(folders::table)
|
db_run! { conn:
|
||||||
.values(&*self)
|
sqlite, mysql {
|
||||||
.on_conflict(folders::uuid)
|
diesel::replace_into(folders::table)
|
||||||
.do_update()
|
.values(FolderDb::to_db(self))
|
||||||
.set(&*self)
|
.execute(conn)
|
||||||
.execute(&**conn)
|
.map_res("Error saving folder")
|
||||||
.map_res("Error saving folder")
|
}
|
||||||
}
|
postgresql {
|
||||||
|
let value = FolderDb::to_db(self);
|
||||||
#[cfg(not(feature = "postgresql"))]
|
diesel::insert_into(folders::table)
|
||||||
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
.values(&value)
|
||||||
User::update_uuid_revision(&self.user_uuid, conn);
|
.on_conflict(folders::uuid)
|
||||||
self.updated_at = Utc::now().naive_utc();
|
.do_update()
|
||||||
|
.set(&value)
|
||||||
diesel::replace_into(folders::table)
|
.execute(conn)
|
||||||
.values(&*self)
|
.map_res("Error saving folder")
|
||||||
.execute(&**conn)
|
}
|
||||||
.map_res("Error saving folder")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||||
User::update_uuid_revision(&self.user_uuid, conn);
|
User::update_uuid_revision(&self.user_uuid, conn);
|
||||||
FolderCipher::delete_all_by_folder(&self.uuid, &conn)?;
|
FolderCipher::delete_all_by_folder(&self.uuid, &conn)?;
|
||||||
|
|
||||||
diesel::delete(folders::table.filter(folders::uuid.eq(&self.uuid)))
|
|
||||||
.execute(&**conn)
|
db_run! { conn: {
|
||||||
.map_res("Error deleting folder")
|
diesel::delete(folders::table.filter(folders::uuid.eq(&self.uuid)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error deleting folder")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -112,73 +114,92 @@ impl Folder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
folders::table
|
db_run! { conn: {
|
||||||
.filter(folders::uuid.eq(uuid))
|
folders::table
|
||||||
.first::<Self>(&**conn)
|
.filter(folders::uuid.eq(uuid))
|
||||||
.ok()
|
.first::<FolderDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
folders::table
|
db_run! { conn: {
|
||||||
.filter(folders::user_uuid.eq(user_uuid))
|
folders::table
|
||||||
.load::<Self>(&**conn)
|
.filter(folders::user_uuid.eq(user_uuid))
|
||||||
.expect("Error loading folders")
|
.load::<FolderDb>(conn)
|
||||||
|
.expect("Error loading folders")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FolderCipher {
|
impl FolderCipher {
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::insert_into(folders_ciphers::table)
|
db_run! { conn:
|
||||||
.values(&*self)
|
sqlite, mysql {
|
||||||
.on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid))
|
diesel::replace_into(folders_ciphers::table)
|
||||||
.do_nothing()
|
.values(FolderCipherDb::to_db(self))
|
||||||
.execute(&**conn)
|
.execute(conn)
|
||||||
.map_res("Error adding cipher to folder")
|
.map_res("Error adding cipher to folder")
|
||||||
}
|
}
|
||||||
|
postgresql {
|
||||||
#[cfg(not(feature = "postgresql"))]
|
diesel::insert_into(folders_ciphers::table)
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
.values(FolderCipherDb::to_db(self))
|
||||||
diesel::replace_into(folders_ciphers::table)
|
.on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid))
|
||||||
.values(&*self)
|
.do_nothing()
|
||||||
.execute(&**conn)
|
.execute(conn)
|
||||||
.map_res("Error adding cipher to folder")
|
.map_res("Error adding cipher to folder")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(
|
db_run! { conn: {
|
||||||
folders_ciphers::table
|
diesel::delete(
|
||||||
.filter(folders_ciphers::cipher_uuid.eq(self.cipher_uuid))
|
folders_ciphers::table
|
||||||
.filter(folders_ciphers::folder_uuid.eq(self.folder_uuid)),
|
.filter(folders_ciphers::cipher_uuid.eq(self.cipher_uuid))
|
||||||
)
|
.filter(folders_ciphers::folder_uuid.eq(self.folder_uuid)),
|
||||||
.execute(&**conn)
|
)
|
||||||
.map_res("Error removing cipher from folder")
|
.execute(conn)
|
||||||
|
.map_res("Error removing cipher from folder")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)))
|
||||||
.map_res("Error removing cipher from folders")
|
.execute(conn)
|
||||||
|
.map_res("Error removing cipher from folders")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_folder(folder_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_folder(folder_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid)))
|
||||||
.map_res("Error removing ciphers from folder")
|
.execute(conn)
|
||||||
|
.map_res("Error removing ciphers from folder")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
folders_ciphers::table
|
db_run! { conn: {
|
||||||
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
folders_ciphers::table
|
||||||
.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))
|
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
||||||
.first::<Self>(&**conn)
|
.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))
|
||||||
.ok()
|
.first::<FolderCipherDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
folders_ciphers::table
|
db_run! { conn: {
|
||||||
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
folders_ciphers::table
|
||||||
.load::<Self>(&**conn)
|
.filter(folders_ciphers::folder_uuid.eq(folder_uuid))
|
||||||
.expect("Error loading folders")
|
.load::<FolderCipherDb>(conn)
|
||||||
|
.expect("Error loading folders")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
use diesel::prelude::*;
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::db::schema::org_policies;
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
|
||||||
use super::Organization;
|
use super::Organization;
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
db_object! {
|
||||||
#[table_name = "org_policies"]
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
||||||
#[belongs_to(Organization, foreign_key = "org_uuid")]
|
#[table_name = "org_policies"]
|
||||||
#[primary_key(uuid)]
|
#[belongs_to(Organization, foreign_key = "org_uuid")]
|
||||||
pub struct OrgPolicy {
|
#[primary_key(uuid)]
|
||||||
pub uuid: String,
|
pub struct OrgPolicy {
|
||||||
pub org_uuid: String,
|
pub uuid: String,
|
||||||
pub atype: i32,
|
pub org_uuid: String,
|
||||||
pub enabled: bool,
|
pub atype: i32,
|
||||||
pub data: String,
|
pub enabled: bool,
|
||||||
|
pub data: String,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -55,87 +55,105 @@ impl OrgPolicy {
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl OrgPolicy {
|
impl OrgPolicy {
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
// We need to make sure we're not going to violate the unique constraint on org_uuid and atype.
|
db_run! { conn:
|
||||||
// This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
|
sqlite, mysql {
|
||||||
// not support multiple constraints on ON CONFLICT clauses.
|
diesel::replace_into(org_policies::table)
|
||||||
diesel::delete(
|
.values(OrgPolicyDb::to_db(self))
|
||||||
org_policies::table
|
.execute(conn)
|
||||||
.filter(org_policies::org_uuid.eq(&self.org_uuid))
|
.map_res("Error saving org_policy")
|
||||||
.filter(org_policies::atype.eq(&self.atype)),
|
}
|
||||||
)
|
postgresql {
|
||||||
.execute(&**conn)
|
let value = OrgPolicyDb::to_db(self);
|
||||||
.map_res("Error deleting org_policy for insert")?;
|
// 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.
|
||||||
|
diesel::delete(
|
||||||
|
org_policies::table
|
||||||
|
.filter(org_policies::org_uuid.eq(&self.org_uuid))
|
||||||
|
.filter(org_policies::atype.eq(&self.atype)),
|
||||||
|
)
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error deleting org_policy for insert")?;
|
||||||
|
|
||||||
diesel::insert_into(org_policies::table)
|
diesel::insert_into(org_policies::table)
|
||||||
.values(self)
|
.values(&value)
|
||||||
.on_conflict(org_policies::uuid)
|
.on_conflict(org_policies::uuid)
|
||||||
.do_update()
|
.do_update()
|
||||||
.set(self)
|
.set(&value)
|
||||||
.execute(&**conn)
|
.execute(conn)
|
||||||
.map_res("Error saving org_policy")
|
.map_res("Error saving org_policy")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[cfg(not(feature = "postgresql"))]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
|
||||||
diesel::replace_into(org_policies::table)
|
|
||||||
.values(&*self)
|
|
||||||
.execute(&**conn)
|
|
||||||
.map_res("Error saving org_policy")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(org_policies::table.filter(org_policies::uuid.eq(self.uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(org_policies::table.filter(org_policies::uuid.eq(self.uuid)))
|
||||||
.map_res("Error deleting org_policy")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting org_policy")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
org_policies::table
|
db_run! { conn: {
|
||||||
.filter(org_policies::uuid.eq(uuid))
|
org_policies::table
|
||||||
.first::<Self>(&**conn)
|
.filter(org_policies::uuid.eq(uuid))
|
||||||
.ok()
|
.first::<OrgPolicyDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
org_policies::table
|
db_run! { conn: {
|
||||||
.filter(org_policies::org_uuid.eq(org_uuid))
|
org_policies::table
|
||||||
.load::<Self>(&**conn)
|
.filter(org_policies::org_uuid.eq(org_uuid))
|
||||||
.expect("Error loading org_policy")
|
.load::<OrgPolicyDb>(conn)
|
||||||
|
.expect("Error loading org_policy")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
use crate::db::schema::users_organizations;
|
db_run! { conn: {
|
||||||
|
org_policies::table
|
||||||
org_policies::table
|
.left_join(
|
||||||
.left_join(
|
users_organizations::table.on(
|
||||||
users_organizations::table.on(
|
users_organizations::org_uuid.eq(org_policies::org_uuid)
|
||||||
users_organizations::org_uuid.eq(org_policies::org_uuid)
|
.and(users_organizations::user_uuid.eq(user_uuid)))
|
||||||
.and(users_organizations::user_uuid.eq(user_uuid)))
|
)
|
||||||
)
|
.select(org_policies::all_columns)
|
||||||
.select(org_policies::all_columns)
|
.load::<OrgPolicyDb>(conn)
|
||||||
.load::<Self>(&**conn)
|
.expect("Error loading org_policy")
|
||||||
.expect("Error loading org_policy")
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> {
|
||||||
org_policies::table
|
db_run! { conn: {
|
||||||
.filter(org_policies::org_uuid.eq(org_uuid))
|
org_policies::table
|
||||||
.filter(org_policies::atype.eq(atype))
|
.filter(org_policies::org_uuid.eq(org_uuid))
|
||||||
.first::<Self>(&**conn)
|
.filter(org_policies::atype.eq(atype))
|
||||||
.ok()
|
.first::<OrgPolicyDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid)))
|
||||||
.map_res("Error deleting org_policy")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting org_policy")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
/*pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
|
||||||
.map_res("Error deleting twofactors")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting twofactors")
|
||||||
|
}}
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,27 +4,29 @@ use num_traits::FromPrimitive;
|
||||||
|
|
||||||
use super::{CollectionUser, User, OrgPolicy};
|
use super::{CollectionUser, User, OrgPolicy};
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
|
db_object! {
|
||||||
#[table_name = "organizations"]
|
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
|
||||||
#[primary_key(uuid)]
|
#[table_name = "organizations"]
|
||||||
pub struct Organization {
|
#[primary_key(uuid)]
|
||||||
pub uuid: String,
|
pub struct Organization {
|
||||||
pub name: String,
|
pub uuid: String,
|
||||||
pub billing_email: String,
|
pub name: String,
|
||||||
}
|
pub billing_email: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
|
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
|
||||||
#[table_name = "users_organizations"]
|
#[table_name = "users_organizations"]
|
||||||
#[primary_key(uuid)]
|
#[primary_key(uuid)]
|
||||||
pub struct UserOrganization {
|
pub struct UserOrganization {
|
||||||
pub uuid: String,
|
pub uuid: String,
|
||||||
pub user_uuid: String,
|
pub user_uuid: String,
|
||||||
pub org_uuid: String,
|
pub org_uuid: String,
|
||||||
|
|
||||||
pub access_all: bool,
|
pub access_all: bool,
|
||||||
pub akey: String,
|
pub akey: String,
|
||||||
pub status: i32,
|
pub status: i32,
|
||||||
pub atype: i32,
|
pub atype: i32,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum UserOrgStatus {
|
pub enum UserOrgStatus {
|
||||||
|
@ -196,16 +198,13 @@ impl UserOrganization {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::db::schema::{ciphers_collections, organizations, users_collections, users_organizations};
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl Organization {
|
impl Organization {
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
UserOrganization::find_by_org(&self.uuid, conn)
|
UserOrganization::find_by_org(&self.uuid, conn)
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -213,27 +212,24 @@ impl Organization {
|
||||||
User::update_uuid_revision(&user_org.user_uuid, conn);
|
User::update_uuid_revision(&user_org.user_uuid, conn);
|
||||||
});
|
});
|
||||||
|
|
||||||
diesel::insert_into(organizations::table)
|
db_run! { conn:
|
||||||
.values(self)
|
sqlite, mysql {
|
||||||
.on_conflict(organizations::uuid)
|
diesel::replace_into(organizations::table)
|
||||||
.do_update()
|
.values(OrganizationDb::to_db(self))
|
||||||
.set(self)
|
.execute(conn)
|
||||||
.execute(&**conn)
|
.map_res("Error saving organization")
|
||||||
.map_res("Error saving organization")
|
}
|
||||||
}
|
postgresql {
|
||||||
|
let value = OrganizationDb::to_db(self);
|
||||||
#[cfg(not(feature = "postgresql"))]
|
diesel::insert_into(organizations::table)
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
.values(&value)
|
||||||
UserOrganization::find_by_org(&self.uuid, conn)
|
.on_conflict(organizations::uuid)
|
||||||
.iter()
|
.do_update()
|
||||||
.for_each(|user_org| {
|
.set(&value)
|
||||||
User::update_uuid_revision(&user_org.user_uuid, conn);
|
.execute(conn)
|
||||||
});
|
.map_res("Error saving organization")
|
||||||
|
}
|
||||||
diesel::replace_into(organizations::table)
|
}
|
||||||
.values(self)
|
|
||||||
.execute(&**conn)
|
|
||||||
.map_res("Error saving organization")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -244,20 +240,27 @@ impl Organization {
|
||||||
UserOrganization::delete_all_by_organization(&self.uuid, &conn)?;
|
UserOrganization::delete_all_by_organization(&self.uuid, &conn)?;
|
||||||
OrgPolicy::delete_all_by_organization(&self.uuid, &conn)?;
|
OrgPolicy::delete_all_by_organization(&self.uuid, &conn)?;
|
||||||
|
|
||||||
diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
|
|
||||||
.execute(&**conn)
|
db_run! { conn: {
|
||||||
.map_res("Error saving organization")
|
diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error saving organization")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
organizations::table
|
db_run! { conn: {
|
||||||
.filter(organizations::uuid.eq(uuid))
|
organizations::table
|
||||||
.first::<Self>(&**conn)
|
.filter(organizations::uuid.eq(uuid))
|
||||||
.ok()
|
.first::<OrganizationDb>(conn)
|
||||||
|
.ok().from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all(conn: &DbConn) -> Vec<Self> {
|
pub fn get_all(conn: &DbConn) -> Vec<Self> {
|
||||||
organizations::table.load::<Self>(&**conn).expect("Error loading organizations")
|
db_run! { conn: {
|
||||||
|
organizations::table.load::<OrganizationDb>(conn).expect("Error loading organizations").from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,28 +348,27 @@ impl UserOrganization {
|
||||||
"Object": "organizationUserDetails",
|
"Object": "organizationUserDetails",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
User::update_uuid_revision(&self.user_uuid, conn);
|
User::update_uuid_revision(&self.user_uuid, conn);
|
||||||
|
|
||||||
diesel::insert_into(users_organizations::table)
|
db_run! { conn:
|
||||||
.values(self)
|
sqlite, mysql {
|
||||||
.on_conflict(users_organizations::uuid)
|
diesel::replace_into(users_organizations::table)
|
||||||
.do_update()
|
.values(UserOrganizationDb::to_db(self))
|
||||||
.set(self)
|
.execute(conn)
|
||||||
.execute(&**conn)
|
.map_res("Error adding user to organization")
|
||||||
.map_res("Error adding user to organization")
|
}
|
||||||
}
|
postgresql {
|
||||||
|
let value = UserOrganizationDb::to_db(self);
|
||||||
#[cfg(not(feature = "postgresql"))]
|
diesel::insert_into(users_organizations::table)
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
.values(&value)
|
||||||
User::update_uuid_revision(&self.user_uuid, conn);
|
.on_conflict(users_organizations::uuid)
|
||||||
|
.do_update()
|
||||||
diesel::replace_into(users_organizations::table)
|
.set(&value)
|
||||||
.values(self)
|
.execute(conn)
|
||||||
.execute(&**conn)
|
.map_res("Error adding user to organization")
|
||||||
.map_res("Error adding user to organization")
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -374,9 +376,11 @@ impl UserOrganization {
|
||||||
|
|
||||||
CollectionUser::delete_all_by_user(&self.user_uuid, &conn)?;
|
CollectionUser::delete_all_by_user(&self.user_uuid, &conn)?;
|
||||||
|
|
||||||
diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid)))
|
||||||
.map_res("Error removing user from organization")
|
.execute(conn)
|
||||||
|
.map_res("Error removing user from organization")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -403,107 +407,129 @@ impl UserOrganization {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::uuid.eq(uuid))
|
users_organizations::table
|
||||||
.first::<Self>(&**conn)
|
.filter(users_organizations::uuid.eq(uuid))
|
||||||
.ok()
|
.first::<UserOrganizationDb>(conn)
|
||||||
|
.ok().from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::uuid.eq(uuid))
|
users_organizations::table
|
||||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
.filter(users_organizations::uuid.eq(uuid))
|
||||||
.first::<Self>(&**conn)
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
.ok()
|
.first::<UserOrganizationDb>(conn)
|
||||||
|
.ok().from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
users_organizations::table
|
||||||
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
.load::<Self>(&**conn)
|
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
||||||
.unwrap_or_default()
|
.load::<UserOrganizationDb>(conn)
|
||||||
|
.unwrap_or_default().from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_invited_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_invited_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
users_organizations::table
|
||||||
.filter(users_organizations::status.eq(UserOrgStatus::Invited as i32))
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
.load::<Self>(&**conn)
|
.filter(users_organizations::status.eq(UserOrgStatus::Invited as i32))
|
||||||
.unwrap_or_default()
|
.load::<UserOrganizationDb>(conn)
|
||||||
|
.unwrap_or_default().from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_any_state_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_any_state_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
users_organizations::table
|
||||||
.load::<Self>(&**conn)
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
.unwrap_or_default()
|
.load::<UserOrganizationDb>(conn)
|
||||||
|
.unwrap_or_default().from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
users_organizations::table
|
||||||
.load::<Self>(&**conn)
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
.expect("Error loading user organizations")
|
.load::<UserOrganizationDb>(conn)
|
||||||
|
.expect("Error loading user organizations").from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
|
pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
users_organizations::table
|
||||||
.count()
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
.first::<i64>(&**conn)
|
.count()
|
||||||
.ok()
|
.first::<i64>(conn)
|
||||||
.unwrap_or(0)
|
.ok()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
users_organizations::table
|
||||||
.filter(users_organizations::atype.eq(atype))
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
.load::<Self>(&**conn)
|
.filter(users_organizations::atype.eq(atype))
|
||||||
.expect("Error loading user organizations")
|
.load::<UserOrganizationDb>(conn)
|
||||||
|
.expect("Error loading user organizations").from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
users_organizations::table
|
||||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
.first::<Self>(&**conn)
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
.ok()
|
.first::<UserOrganizationDb>(conn)
|
||||||
|
.ok().from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
users_organizations::table
|
||||||
.left_join(users_collections::table.on(
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
users_collections::user_uuid.eq(users_organizations::user_uuid)
|
.left_join(users_collections::table.on(
|
||||||
))
|
users_collections::user_uuid.eq(users_organizations::user_uuid)
|
||||||
.left_join(ciphers_collections::table.on(
|
))
|
||||||
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid).and(
|
.left_join(ciphers_collections::table.on(
|
||||||
ciphers_collections::cipher_uuid.eq(&cipher_uuid)
|
ciphers_collections::collection_uuid.eq(users_collections::collection_uuid).and(
|
||||||
|
ciphers_collections::cipher_uuid.eq(&cipher_uuid)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.filter(
|
||||||
|
users_organizations::access_all.eq(true).or( // AccessAll..
|
||||||
|
ciphers_collections::cipher_uuid.eq(&cipher_uuid) // ..or access to collection with cipher
|
||||||
|
)
|
||||||
)
|
)
|
||||||
))
|
.select(users_organizations::all_columns)
|
||||||
.filter(
|
.load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db()
|
||||||
users_organizations::access_all.eq(true).or( // AccessAll..
|
}}
|
||||||
ciphers_collections::cipher_uuid.eq(&cipher_uuid) // ..or access to collection with cipher
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.select(users_organizations::all_columns)
|
|
||||||
.load::<Self>(&**conn).expect("Error loading user organizations")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_collection_and_org(collection_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_collection_and_org(collection_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
users_organizations::table
|
db_run! { conn: {
|
||||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
users_organizations::table
|
||||||
.left_join(users_collections::table.on(
|
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||||
users_collections::user_uuid.eq(users_organizations::user_uuid)
|
.left_join(users_collections::table.on(
|
||||||
))
|
users_collections::user_uuid.eq(users_organizations::user_uuid)
|
||||||
.filter(
|
))
|
||||||
users_organizations::access_all.eq(true).or( // AccessAll..
|
.filter(
|
||||||
users_collections::collection_uuid.eq(&collection_uuid) // ..or access to collection with cipher
|
users_organizations::access_all.eq(true).or( // AccessAll..
|
||||||
|
users_collections::collection_uuid.eq(&collection_uuid) // ..or access to collection with cipher
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
.select(users_organizations::all_columns)
|
||||||
.select(users_organizations::all_columns)
|
.load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db()
|
||||||
.load::<Self>(&**conn).expect("Error loading user organizations")
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
use diesel::prelude::*;
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::db::schema::twofactor;
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
|
||||||
use super::User;
|
use super::User;
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
db_object! {
|
||||||
#[table_name = "twofactor"]
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
|
||||||
#[belongs_to(User, foreign_key = "user_uuid")]
|
#[table_name = "twofactor"]
|
||||||
#[primary_key(uuid)]
|
#[belongs_to(User, foreign_key = "user_uuid")]
|
||||||
pub struct TwoFactor {
|
#[primary_key(uuid)]
|
||||||
pub uuid: String,
|
pub struct TwoFactor {
|
||||||
pub user_uuid: String,
|
pub uuid: String,
|
||||||
pub atype: i32,
|
pub user_uuid: String,
|
||||||
pub enabled: bool,
|
pub atype: i32,
|
||||||
pub data: String,
|
pub enabled: bool,
|
||||||
pub last_used: i32,
|
pub data: String,
|
||||||
|
pub last_used: i32,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -70,57 +70,69 @@ impl TwoFactor {
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl TwoFactor {
|
impl TwoFactor {
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
// We need to make sure we're not going to violate the unique constraint on user_uuid and atype.
|
db_run! { conn:
|
||||||
// This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
|
sqlite, mysql {
|
||||||
// not support multiple constraints on ON CONFLICT clauses.
|
diesel::replace_into(twofactor::table)
|
||||||
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(&self.user_uuid)).filter(twofactor::atype.eq(&self.atype)))
|
.values(TwoFactorDb::to_db(self))
|
||||||
.execute(&**conn)
|
.execute(conn)
|
||||||
.map_res("Error deleting twofactor for insert")?;
|
.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.
|
||||||
|
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(&self.user_uuid)).filter(twofactor::atype.eq(&self.atype)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error deleting twofactor for insert")?;
|
||||||
|
|
||||||
diesel::insert_into(twofactor::table)
|
diesel::insert_into(twofactor::table)
|
||||||
.values(self)
|
.values(&value)
|
||||||
.on_conflict(twofactor::uuid)
|
.on_conflict(twofactor::uuid)
|
||||||
.do_update()
|
.do_update()
|
||||||
.set(self)
|
.set(&value)
|
||||||
.execute(&**conn)
|
.execute(conn)
|
||||||
.map_res("Error saving twofactor")
|
.map_res("Error saving twofactor")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[cfg(not(feature = "postgresql"))]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
|
||||||
diesel::replace_into(twofactor::table)
|
|
||||||
.values(self)
|
|
||||||
.execute(&**conn)
|
|
||||||
.map_res("Error saving twofactor")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(twofactor::table.filter(twofactor::uuid.eq(self.uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(twofactor::table.filter(twofactor::uuid.eq(self.uuid)))
|
||||||
.map_res("Error deleting twofactor")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting twofactor")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
twofactor::table
|
db_run! { conn: {
|
||||||
.filter(twofactor::user_uuid.eq(user_uuid))
|
twofactor::table
|
||||||
.filter(twofactor::atype.lt(1000)) // Filter implementation types
|
.filter(twofactor::user_uuid.eq(user_uuid))
|
||||||
.load::<Self>(&**conn)
|
.filter(twofactor::atype.lt(1000)) // Filter implementation types
|
||||||
.expect("Error loading twofactor")
|
.load::<TwoFactorDb>(conn)
|
||||||
|
.expect("Error loading twofactor")
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> {
|
||||||
twofactor::table
|
db_run! { conn: {
|
||||||
.filter(twofactor::user_uuid.eq(user_uuid))
|
twofactor::table
|
||||||
.filter(twofactor::atype.eq(atype))
|
.filter(twofactor::user_uuid.eq(user_uuid))
|
||||||
.first::<Self>(&**conn)
|
.filter(twofactor::atype.eq(atype))
|
||||||
.ok()
|
.first::<TwoFactorDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
|
db_run! { conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
|
||||||
.map_res("Error deleting twofactors")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting twofactors")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,43 +4,53 @@ use serde_json::Value;
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
|
db_object! {
|
||||||
#[table_name = "users"]
|
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
|
||||||
#[changeset_options(treat_none_as_null="true")]
|
#[table_name = "users"]
|
||||||
#[primary_key(uuid)]
|
#[changeset_options(treat_none_as_null="true")]
|
||||||
pub struct User {
|
#[primary_key(uuid)]
|
||||||
pub uuid: String,
|
pub struct User {
|
||||||
pub created_at: NaiveDateTime,
|
pub uuid: String,
|
||||||
pub updated_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub verified_at: Option<NaiveDateTime>,
|
pub updated_at: NaiveDateTime,
|
||||||
pub last_verifying_at: Option<NaiveDateTime>,
|
pub verified_at: Option<NaiveDateTime>,
|
||||||
pub login_verify_count: i32,
|
pub last_verifying_at: Option<NaiveDateTime>,
|
||||||
|
pub login_verify_count: i32,
|
||||||
|
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub email_new: Option<String>,
|
pub email_new: Option<String>,
|
||||||
pub email_new_token: Option<String>,
|
pub email_new_token: Option<String>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
pub password_hash: Vec<u8>,
|
pub password_hash: Vec<u8>,
|
||||||
pub salt: Vec<u8>,
|
pub salt: Vec<u8>,
|
||||||
pub password_iterations: i32,
|
pub password_iterations: i32,
|
||||||
pub password_hint: Option<String>,
|
pub password_hint: Option<String>,
|
||||||
|
|
||||||
pub akey: String,
|
pub akey: String,
|
||||||
pub private_key: Option<String>,
|
pub private_key: Option<String>,
|
||||||
pub public_key: Option<String>,
|
pub public_key: Option<String>,
|
||||||
|
|
||||||
#[column_name = "totp_secret"]
|
#[column_name = "totp_secret"] // Note, this is only added to the UserDb structs, not to User
|
||||||
_totp_secret: Option<String>,
|
_totp_secret: Option<String>,
|
||||||
pub totp_recover: Option<String>,
|
pub totp_recover: Option<String>,
|
||||||
|
|
||||||
pub security_stamp: String,
|
pub security_stamp: String,
|
||||||
|
|
||||||
pub equivalent_domains: String,
|
pub equivalent_domains: String,
|
||||||
pub excluded_globals: String,
|
pub excluded_globals: String,
|
||||||
|
|
||||||
pub client_kdf_type: i32,
|
pub client_kdf_type: i32,
|
||||||
pub client_kdf_iter: i32,
|
pub client_kdf_iter: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Identifiable, Queryable, Insertable)]
|
||||||
|
#[table_name = "invitations"]
|
||||||
|
#[primary_key(email)]
|
||||||
|
pub struct Invitation {
|
||||||
|
pub email: String,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UserStatus {
|
enum UserStatus {
|
||||||
|
@ -119,9 +129,7 @@ impl User {
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::{Cipher, Device, Folder, TwoFactor, UserOrgType, UserOrganization};
|
use super::{Cipher, Device, Folder, TwoFactor, UserOrgType, UserOrganization};
|
||||||
use crate::db::schema::{invitations, users};
|
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
@ -158,7 +166,6 @@ impl User {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
||||||
if self.email.trim().is_empty() {
|
if self.email.trim().is_empty() {
|
||||||
err!("User email can't be empty")
|
err!("User email can't be empty")
|
||||||
|
@ -166,49 +173,48 @@ impl User {
|
||||||
|
|
||||||
self.updated_at = Utc::now().naive_utc();
|
self.updated_at = Utc::now().naive_utc();
|
||||||
|
|
||||||
diesel::insert_into(users::table) // Insert or update
|
db_run! {conn:
|
||||||
.values(&*self)
|
sqlite, mysql {
|
||||||
.on_conflict(users::uuid)
|
diesel::replace_into(users::table) // Insert or update
|
||||||
.do_update()
|
.values(&UserDb::to_db(self))
|
||||||
.set(&*self)
|
.execute(conn)
|
||||||
.execute(&**conn)
|
.map_res("Error saving user")
|
||||||
.map_res("Error saving user")
|
}
|
||||||
}
|
postgresql {
|
||||||
|
let value = UserDb::to_db(self);
|
||||||
#[cfg(not(feature = "postgresql"))]
|
diesel::insert_into(users::table) // Insert or update
|
||||||
pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
|
.values(&value)
|
||||||
if self.email.trim().is_empty() {
|
.on_conflict(users::uuid)
|
||||||
err!("User email can't be empty")
|
.do_update()
|
||||||
|
.set(&value)
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error saving user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updated_at = Utc::now().naive_utc();
|
|
||||||
|
|
||||||
diesel::replace_into(users::table) // Insert or update
|
|
||||||
.values(&*self)
|
|
||||||
.execute(&**conn)
|
|
||||||
.map_res("Error saving user")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
for user_org in UserOrganization::find_by_user(&self.uuid, &*conn) {
|
for user_org in UserOrganization::find_by_user(&self.uuid, conn) {
|
||||||
if user_org.atype == UserOrgType::Owner {
|
if user_org.atype == UserOrgType::Owner {
|
||||||
let owner_type = UserOrgType::Owner as i32;
|
let owner_type = UserOrgType::Owner as i32;
|
||||||
if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, &conn).len() <= 1 {
|
if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, conn).len() <= 1 {
|
||||||
err!("Can't delete last owner")
|
err!("Can't delete last owner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UserOrganization::delete_all_by_user(&self.uuid, &*conn)?;
|
UserOrganization::delete_all_by_user(&self.uuid, conn)?;
|
||||||
Cipher::delete_all_by_user(&self.uuid, &*conn)?;
|
Cipher::delete_all_by_user(&self.uuid, conn)?;
|
||||||
Folder::delete_all_by_user(&self.uuid, &*conn)?;
|
Folder::delete_all_by_user(&self.uuid, conn)?;
|
||||||
Device::delete_all_by_user(&self.uuid, &*conn)?;
|
Device::delete_all_by_user(&self.uuid, conn)?;
|
||||||
TwoFactor::delete_all_by_user(&self.uuid, &*conn)?;
|
TwoFactor::delete_all_by_user(&self.uuid, conn)?;
|
||||||
Invitation::take(&self.email, &*conn); // Delete invitation if any
|
Invitation::take(&self.email, conn); // Delete invitation if any
|
||||||
|
|
||||||
diesel::delete(users::table.filter(users::uuid.eq(self.uuid)))
|
db_run! {conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(users::table.filter(users::uuid.eq(self.uuid)))
|
||||||
.map_res("Error deleting user")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting user")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_uuid_revision(uuid: &str, conn: &DbConn) {
|
pub fn update_uuid_revision(uuid: &str, conn: &DbConn) {
|
||||||
|
@ -220,15 +226,14 @@ impl User {
|
||||||
pub fn update_all_revisions(conn: &DbConn) -> EmptyResult {
|
pub fn update_all_revisions(conn: &DbConn) -> EmptyResult {
|
||||||
let updated_at = Utc::now().naive_utc();
|
let updated_at = Utc::now().naive_utc();
|
||||||
|
|
||||||
crate::util::retry(
|
db_run! {conn: {
|
||||||
|| {
|
crate::util::retry(|| {
|
||||||
diesel::update(users::table)
|
diesel::update(users::table)
|
||||||
.set(users::updated_at.eq(updated_at))
|
.set(users::updated_at.eq(updated_at))
|
||||||
.execute(&**conn)
|
.execute(conn)
|
||||||
},
|
}, 10)
|
||||||
10,
|
.map_res("Error updating revision date for all users")
|
||||||
)
|
}}
|
||||||
.map_res("Error updating revision date for all users")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_revision(&mut self, conn: &DbConn) -> EmptyResult {
|
pub fn update_revision(&mut self, conn: &DbConn) -> EmptyResult {
|
||||||
|
@ -238,84 +243,85 @@ impl User {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
|
fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
|
||||||
crate::util::retry(
|
db_run! {conn: {
|
||||||
|| {
|
crate::util::retry(|| {
|
||||||
diesel::update(users::table.filter(users::uuid.eq(uuid)))
|
diesel::update(users::table.filter(users::uuid.eq(uuid)))
|
||||||
.set(users::updated_at.eq(date))
|
.set(users::updated_at.eq(date))
|
||||||
.execute(&**conn)
|
.execute(conn)
|
||||||
},
|
}, 10)
|
||||||
10,
|
.map_res("Error updating user revision")
|
||||||
)
|
}}
|
||||||
.map_res("Error updating user revision")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
|
||||||
let lower_mail = mail.to_lowercase();
|
let lower_mail = mail.to_lowercase();
|
||||||
users::table
|
db_run! {conn: {
|
||||||
.filter(users::email.eq(lower_mail))
|
users::table
|
||||||
.first::<Self>(&**conn)
|
.filter(users::email.eq(lower_mail))
|
||||||
.ok()
|
.first::<UserDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
users::table.filter(users::uuid.eq(uuid)).first::<Self>(&**conn).ok()
|
db_run! {conn: {
|
||||||
|
users::table.filter(users::uuid.eq(uuid)).first::<UserDb>(conn).ok().from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all(conn: &DbConn) -> Vec<Self> {
|
pub fn get_all(conn: &DbConn) -> Vec<Self> {
|
||||||
users::table.load::<Self>(&**conn).expect("Error loading users")
|
db_run! {conn: {
|
||||||
|
users::table.load::<UserDb>(conn).expect("Error loading users").from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable)]
|
|
||||||
#[table_name = "invitations"]
|
|
||||||
#[primary_key(email)]
|
|
||||||
pub struct Invitation {
|
|
||||||
pub email: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Invitation {
|
impl Invitation {
|
||||||
pub const fn new(email: String) -> Self {
|
pub const fn new(email: String) -> Self {
|
||||||
Self { email }
|
Self { email }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
if self.email.trim().is_empty() {
|
if self.email.trim().is_empty() {
|
||||||
err!("Invitation email can't be empty")
|
err!("Invitation email can't be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::insert_into(invitations::table)
|
db_run! {conn:
|
||||||
.values(self)
|
sqlite, mysql {
|
||||||
.on_conflict(invitations::email)
|
diesel::replace_into(invitations::table)
|
||||||
.do_nothing()
|
.values(InvitationDb::to_db(self))
|
||||||
.execute(&**conn)
|
.execute(conn)
|
||||||
.map_res("Error saving invitation")
|
.map_res("Error saving invitation")
|
||||||
}
|
}
|
||||||
|
postgresql {
|
||||||
#[cfg(not(feature = "postgresql"))]
|
diesel::insert_into(invitations::table)
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
.values(InvitationDb::to_db(self))
|
||||||
if self.email.trim().is_empty() {
|
.on_conflict(invitations::email)
|
||||||
err!("Invitation email can't be empty")
|
.do_nothing()
|
||||||
|
.execute(conn)
|
||||||
|
.map_res("Error saving invitation")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::replace_into(invitations::table)
|
|
||||||
.values(self)
|
|
||||||
.execute(&**conn)
|
|
||||||
.map_res("Error saving invitation")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub fn delete(self, conn: &DbConn) -> EmptyResult {
|
||||||
diesel::delete(invitations::table.filter(invitations::email.eq(self.email)))
|
db_run! {conn: {
|
||||||
.execute(&**conn)
|
diesel::delete(invitations::table.filter(invitations::email.eq(self.email)))
|
||||||
.map_res("Error deleting invitation")
|
.execute(conn)
|
||||||
|
.map_res("Error deleting invitation")
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
|
||||||
let lower_mail = mail.to_lowercase();
|
let lower_mail = mail.to_lowercase();
|
||||||
invitations::table
|
db_run! {conn: {
|
||||||
.filter(invitations::email.eq(lower_mail))
|
invitations::table
|
||||||
.first::<Self>(&**conn)
|
.filter(invitations::email.eq(lower_mail))
|
||||||
.ok()
|
.first::<InvitationDb>(conn)
|
||||||
|
.ok()
|
||||||
|
.from_db()
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take(mail: &str, conn: &DbConn) -> bool {
|
pub fn take(mail: &str, conn: &DbConn) -> bool {
|
||||||
|
|
|
@ -34,6 +34,7 @@ macro_rules! make_error {
|
||||||
}
|
}
|
||||||
|
|
||||||
use diesel::result::Error as DieselErr;
|
use diesel::result::Error as DieselErr;
|
||||||
|
use diesel::r2d2::PoolError as R2d2Err;
|
||||||
use handlebars::RenderError as HbErr;
|
use handlebars::RenderError as HbErr;
|
||||||
use jsonwebtoken::errors::Error as JWTErr;
|
use jsonwebtoken::errors::Error as JWTErr;
|
||||||
use regex::Error as RegexErr;
|
use regex::Error as RegexErr;
|
||||||
|
@ -66,6 +67,7 @@ make_error! {
|
||||||
// Used for special return values, like 2FA errors
|
// Used for special return values, like 2FA errors
|
||||||
JsonError(Value): _no_source, _serialize,
|
JsonError(Value): _no_source, _serialize,
|
||||||
DbError(DieselErr): _has_source, _api_error,
|
DbError(DieselErr): _has_source, _api_error,
|
||||||
|
R2d2Error(R2d2Err): _has_source, _api_error,
|
||||||
U2fError(U2fErr): _has_source, _api_error,
|
U2fError(U2fErr): _has_source, _api_error,
|
||||||
SerdeError(SerdeErr): _has_source, _api_error,
|
SerdeError(SerdeErr): _has_source, _api_error,
|
||||||
JWTError(JWTErr): _has_source, _api_error,
|
JWTError(JWTErr): _has_source, _api_error,
|
||||||
|
|
82
src/main.rs
82
src/main.rs
|
@ -33,6 +33,7 @@ mod api;
|
||||||
mod auth;
|
mod auth;
|
||||||
mod config;
|
mod config;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
#[macro_use]
|
||||||
mod db;
|
mod db;
|
||||||
mod mail;
|
mod mail;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -61,10 +62,8 @@ fn main() {
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
check_db();
|
|
||||||
check_rsa_keys();
|
check_rsa_keys();
|
||||||
check_web_vault();
|
check_web_vault();
|
||||||
migrations::run_migrations();
|
|
||||||
|
|
||||||
create_icon_cache_folder();
|
create_icon_cache_folder();
|
||||||
|
|
||||||
|
@ -200,30 +199,6 @@ fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_db() {
|
|
||||||
if cfg!(feature = "sqlite") {
|
|
||||||
let url = CONFIG.database_url();
|
|
||||||
let path = Path::new(&url);
|
|
||||||
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
if create_dir_all(parent).is_err() {
|
|
||||||
error!("Error creating database directory");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn on WAL in SQLite
|
|
||||||
if CONFIG.enable_db_wal() {
|
|
||||||
use diesel::RunQueryDsl;
|
|
||||||
let connection = db::get_connection().expect("Can't connect to DB");
|
|
||||||
diesel::sql_query("PRAGMA journal_mode=wal")
|
|
||||||
.execute(&connection)
|
|
||||||
.expect("Failed to turn on WAL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db::get_connection().expect("Can't connect to DB");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_icon_cache_folder() {
|
fn create_icon_cache_folder() {
|
||||||
// Try to create the icon cache folder, and generate an error if it could not.
|
// Try to create the icon cache folder, and generate an error if it could not.
|
||||||
create_dir_all(&CONFIG.icon_cache_folder()).expect("Error creating icon cache directory");
|
create_dir_all(&CONFIG.icon_cache_folder()).expect("Error creating icon cache directory");
|
||||||
|
@ -285,57 +260,22 @@ fn check_web_vault() {
|
||||||
let index_path = Path::new(&CONFIG.web_vault_folder()).join("index.html");
|
let index_path = Path::new(&CONFIG.web_vault_folder()).join("index.html");
|
||||||
|
|
||||||
if !index_path.exists() {
|
if !index_path.exists() {
|
||||||
error!("Web vault is not found. To install it, please follow the steps in: ");
|
error!("Web vault is not found at '{}'. To install it, please follow the steps in: ", CONFIG.web_vault_folder());
|
||||||
error!("https://github.com/dani-garcia/bitwarden_rs/wiki/Building-binary#install-the-web-vault");
|
error!("https://github.com/dani-garcia/bitwarden_rs/wiki/Building-binary#install-the-web-vault");
|
||||||
error!("You can also set the environment variable 'WEB_VAULT_ENABLED=false' to disable it");
|
error!("You can also set the environment variable 'WEB_VAULT_ENABLED=false' to disable it");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Embed the migrations from the migrations folder into the application
|
|
||||||
// This way, the program automatically migrates the database to the latest version
|
|
||||||
// https://docs.rs/diesel_migrations/*/diesel_migrations/macro.embed_migrations.html
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
mod migrations {
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
|
||||||
embed_migrations!("migrations/sqlite");
|
|
||||||
#[cfg(feature = "mysql")]
|
|
||||||
embed_migrations!("migrations/mysql");
|
|
||||||
#[cfg(feature = "postgresql")]
|
|
||||||
embed_migrations!("migrations/postgresql");
|
|
||||||
|
|
||||||
pub fn run_migrations() {
|
|
||||||
// Make sure the database is up to date (create if it doesn't exist, or run the migrations)
|
|
||||||
let connection = crate::db::get_connection().expect("Can't connect to DB");
|
|
||||||
|
|
||||||
use std::io::stdout;
|
|
||||||
|
|
||||||
// Disable Foreign Key Checks during migration
|
|
||||||
use diesel::RunQueryDsl;
|
|
||||||
|
|
||||||
// FIXME: Per https://www.postgresql.org/docs/12/sql-set-constraints.html,
|
|
||||||
// "SET CONSTRAINTS sets the behavior of constraint checking within the
|
|
||||||
// current transaction", so this setting probably won't take effect for
|
|
||||||
// any of the migrations since it's being run outside of a transaction.
|
|
||||||
// Migrations that need to disable foreign key checks should run this
|
|
||||||
// from within the migration script itself.
|
|
||||||
#[cfg(feature = "postgres")]
|
|
||||||
diesel::sql_query("SET CONSTRAINTS ALL DEFERRED").execute(&connection).expect("Failed to disable Foreign Key Checks during migrations");
|
|
||||||
|
|
||||||
// Scoped to a connection/session.
|
|
||||||
#[cfg(feature = "mysql")]
|
|
||||||
diesel::sql_query("SET FOREIGN_KEY_CHECKS = 0").execute(&connection).expect("Failed to disable Foreign Key Checks during migrations");
|
|
||||||
|
|
||||||
// Scoped to a connection.
|
|
||||||
#[cfg(feature = "sqlite")]
|
|
||||||
diesel::sql_query("PRAGMA foreign_keys = OFF").execute(&connection).expect("Failed to disable Foreign Key Checks during migrations");
|
|
||||||
|
|
||||||
embedded_migrations::run_with_output(&connection, &mut stdout()).expect("Can't run migrations");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn launch_rocket(extra_debug: bool) {
|
fn launch_rocket(extra_debug: bool) {
|
||||||
|
let pool = match db::DbPool::from_config() {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error creating database pool: {:?}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let basepath = &CONFIG.domain_path();
|
let basepath = &CONFIG.domain_path();
|
||||||
|
|
||||||
// If adding more paths here, consider also adding them to
|
// If adding more paths here, consider also adding them to
|
||||||
|
@ -347,7 +287,7 @@ fn launch_rocket(extra_debug: bool) {
|
||||||
.mount(&[basepath, "/identity"].concat(), api::identity_routes())
|
.mount(&[basepath, "/identity"].concat(), api::identity_routes())
|
||||||
.mount(&[basepath, "/icons"].concat(), api::icons_routes())
|
.mount(&[basepath, "/icons"].concat(), api::icons_routes())
|
||||||
.mount(&[basepath, "/notifications"].concat(), api::notifications_routes())
|
.mount(&[basepath, "/notifications"].concat(), api::notifications_routes())
|
||||||
.manage(db::init_pool())
|
.manage(pool)
|
||||||
.manage(api::start_notification_server())
|
.manage(api::start_notification_server())
|
||||||
.attach(util::AppHeaders())
|
.attach(util::AppHeaders())
|
||||||
.attach(util::CORS())
|
.attach(util::CORS())
|
||||||
|
|
Laden …
In neuem Issue referenzieren