1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2024-07-02 12:14:41 +02:00

Use Diesels MultiConnections Derive

With this PR we remove allmost all custom macro's to create the multiple
database type code. This is now handled by Diesel it self.

This removed the need of the following functions/macro's:
- `db_object!`
- `::to_db`
- `.from_db()`

All `conn` variables do not need a `mut` anymore (says nightly rust)

It is also possible to just use one schema instead of multiple per type.
Dieser Commit ist enthalten in:
BlackDex 2023-08-24 22:06:32 +02:00
Ursprung 6eaf131922
Commit 1b1596ce1e
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 58C80A2AA6C765E1
44 geänderte Dateien mit 1883 neuen und 2901 gelöschten Zeilen

11
Cargo.lock generiert
Datei anzeigen

@ -722,16 +722,6 @@ dependencies = [
"syn 2.0.29", "syn 2.0.29",
] ]
[[package]]
name = "diesel_logger"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23010b507517129dc9b11fb35f36d76fd2d3dd4c85232733697622e345375f2f"
dependencies = [
"diesel",
"log",
]
[[package]] [[package]]
name = "diesel_migrations" name = "diesel_migrations"
version = "2.1.0" version = "2.1.0"
@ -3469,7 +3459,6 @@ dependencies = [
"data-encoding", "data-encoding",
"data-url", "data-url",
"diesel", "diesel",
"diesel_logger",
"diesel_migrations", "diesel_migrations",
"dotenvy", "dotenvy",
"email_address", "email_address",

Datei anzeigen

@ -13,7 +13,11 @@ publish = false
build = "build.rs" build = "build.rs"
[features] [features]
# default = ["sqlite"] default = [
# "sqlite",
# "postgresql",
# "mysql",
]
# Empty to keep compatibility, prefer to set USE_SYSLOG=true # Empty to keep compatibility, prefer to set USE_SYSLOG=true
enable_syslog = [] enable_syslog = []
mysql = ["diesel/mysql", "diesel_migrations/mysql"] mysql = ["diesel/mysql", "diesel_migrations/mysql"]
@ -28,7 +32,7 @@ enable_mimalloc = ["mimalloc"]
# It enables the usage of the diesel_logger crate, which is able to output the generated queries. # It enables the usage of the diesel_logger crate, which is able to output the generated queries.
# You also need to set an env variable `QUERY_LOGGER=1` to fully activate this so you do not have to re-compile # You also need to set an env variable `QUERY_LOGGER=1` to fully activate this so you do not have to re-compile
# if you want to turn off the logging for a specific run. # if you want to turn off the logging for a specific run.
query_logger = ["diesel_logger"] # query_logger = ["diesel_logger"] # Currently not able to be used with MultiConnection.
# Enable unstable features, requires nightly # Enable unstable features, requires nightly
# Currently only used to enable rusts official ip support # Currently only used to enable rusts official ip support
@ -77,7 +81,7 @@ serde_json = "1.0.105"
# A safe, extensible ORM and Query builder # A safe, extensible ORM and Query builder
diesel = { version = "2.1.1", features = ["chrono", "r2d2"] } diesel = { version = "2.1.1", features = ["chrono", "r2d2"] }
diesel_migrations = "2.1.0" diesel_migrations = "2.1.0"
diesel_logger = { version = "0.3.0", optional = true } # diesel_logger = { version = "0.3.0", optional = true } # Currently not able to be used with MultiConnection.
# Bundled/Static SQLite # Bundled/Static SQLite
libsqlite3-sys = { version = "0.26.0", features = ["bundled"], optional = true } libsqlite3-sys = { version = "0.26.0", features = ["bundled"], optional = true }

Datei anzeigen

@ -70,15 +70,22 @@ pub fn catchers() -> Vec<Catcher> {
static DB_TYPE: Lazy<&str> = Lazy::new(|| { static DB_TYPE: Lazy<&str> = Lazy::new(|| {
DbConnType::from_url(&CONFIG.database_url()) DbConnType::from_url(&CONFIG.database_url())
.map(|t| match t { .map(|t| match t {
DbConnType::sqlite => "SQLite", #[cfg(sqlite)]
DbConnType::mysql => "MySQL", DbConnType::Sqlite => "SQLite",
DbConnType::postgresql => "PostgreSQL", #[cfg(mysql)]
DbConnType::Mysql => "MySQL",
#[cfg(postgresql)]
DbConnType::Postgresql => "PostgreSQL",
}) })
.unwrap_or("Unknown") .unwrap_or("Unknown")
}); });
#[cfg(sqlite)]
static CAN_BACKUP: Lazy<bool> = static CAN_BACKUP: Lazy<bool> =
Lazy::new(|| DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::sqlite).unwrap_or(false)); Lazy::new(|| DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::Sqlite).unwrap_or(false));
#[cfg(not(sqlite))]
static CAN_BACKUP: Lazy<bool> = Lazy::new(|| false);
#[get("/")] #[get("/")]
fn admin_disabled() -> &'static str { fn admin_disabled() -> &'static str {
@ -268,7 +275,7 @@ struct InviteData {
email: String, email: String,
} }
async fn get_user_or_404(uuid: &str, conn: &mut DbConn) -> ApiResult<User> { async fn get_user_or_404(uuid: &str, conn: &DbConn) -> ApiResult<User> {
if let Some(user) = User::find_by_uuid(uuid, conn).await { if let Some(user) = User::find_by_uuid(uuid, conn).await {
Ok(user) Ok(user)
} else { } else {
@ -277,15 +284,15 @@ async fn get_user_or_404(uuid: &str, conn: &mut DbConn) -> ApiResult<User> {
} }
#[post("/invite", data = "<data>")] #[post("/invite", data = "<data>")]
async fn invite_user(data: Json<InviteData>, _token: AdminToken, mut conn: DbConn) -> JsonResult { async fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -> JsonResult {
let data: InviteData = data.into_inner(); let data: InviteData = data.into_inner();
if User::find_by_mail(&data.email, &mut conn).await.is_some() { if User::find_by_mail(&data.email, &conn).await.is_some() {
err_code!("User already exists", Status::Conflict.code) err_code!("User already exists", Status::Conflict.code)
} }
let mut user = User::new(data.email); let mut user = User::new(data.email);
async fn _generate_invite(user: &User, conn: &mut DbConn) -> EmptyResult { async fn _generate_invite(user: &User, conn: &DbConn) -> EmptyResult {
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None).await mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None).await
} else { } else {
@ -294,10 +301,10 @@ async fn invite_user(data: Json<InviteData>, _token: AdminToken, mut conn: DbCon
} }
} }
_generate_invite(&user, &mut conn).await.map_err(|e| e.with_code(Status::InternalServerError.code))?; _generate_invite(&user, &conn).await.map_err(|e| e.with_code(Status::InternalServerError.code))?;
user.save(&mut conn).await.map_err(|e| e.with_code(Status::InternalServerError.code))?; user.save(&conn).await.map_err(|e| e.with_code(Status::InternalServerError.code))?;
Ok(Json(user.to_json(&mut conn).await)) Ok(Json(user.to_json(&conn).await))
} }
#[post("/test/smtp", data = "<data>")] #[post("/test/smtp", data = "<data>")]
@ -318,14 +325,14 @@ fn logout(cookies: &CookieJar<'_>) -> Redirect {
} }
#[get("/users")] #[get("/users")]
async fn get_users_json(_token: AdminToken, mut conn: DbConn) -> Json<Value> { async fn get_users_json(_token: AdminToken, conn: DbConn) -> Json<Value> {
let users = User::get_all(&mut conn).await; let users = User::get_all(&conn).await;
let mut users_json = Vec::with_capacity(users.len()); let mut users_json = Vec::with_capacity(users.len());
for u in users { for u in users {
let mut usr = u.to_json(&mut conn).await; let mut usr = u.to_json(&conn).await;
usr["UserEnabled"] = json!(u.enabled); usr["UserEnabled"] = json!(u.enabled);
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
usr["LastActive"] = match u.last_active(&mut conn).await { usr["LastActive"] = match u.last_active(&conn).await {
Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)), Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)),
None => json!(None::<String>), None => json!(None::<String>),
}; };
@ -336,17 +343,17 @@ async fn get_users_json(_token: AdminToken, mut conn: DbConn) -> Json<Value> {
} }
#[get("/users/overview")] #[get("/users/overview")]
async fn users_overview(_token: AdminToken, mut conn: DbConn) -> ApiResult<Html<String>> { async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
let users = User::get_all(&mut conn).await; let users = User::get_all(&conn).await;
let mut users_json = Vec::with_capacity(users.len()); let mut users_json = Vec::with_capacity(users.len());
for u in users { for u in users {
let mut usr = u.to_json(&mut conn).await; let mut usr = u.to_json(&conn).await;
usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &mut conn).await); usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn).await);
usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, &mut conn).await); usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, &conn).await);
usr["attachment_size"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, &mut conn).await as i32)); usr["attachment_size"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, &conn).await as i32));
usr["user_enabled"] = json!(u.enabled); usr["user_enabled"] = json!(u.enabled);
usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
usr["last_active"] = match u.last_active(&mut conn).await { usr["last_active"] = match u.last_active(&conn).await {
Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)), Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)),
None => json!("Never"), None => json!("Never"),
}; };
@ -358,9 +365,9 @@ async fn users_overview(_token: AdminToken, mut conn: DbConn) -> ApiResult<Html<
} }
#[get("/users/by-mail/<mail>")] #[get("/users/by-mail/<mail>")]
async fn get_user_by_mail_json(mail: &str, _token: AdminToken, mut conn: DbConn) -> JsonResult { async fn get_user_by_mail_json(mail: &str, _token: AdminToken, conn: DbConn) -> JsonResult {
if let Some(u) = User::find_by_mail(mail, &mut conn).await { if let Some(u) = User::find_by_mail(mail, &conn).await {
let mut usr = u.to_json(&mut conn).await; let mut usr = u.to_json(&conn).await;
usr["UserEnabled"] = json!(u.enabled); usr["UserEnabled"] = json!(u.enabled);
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
Ok(Json(usr)) Ok(Json(usr))
@ -370,21 +377,21 @@ async fn get_user_by_mail_json(mail: &str, _token: AdminToken, mut conn: DbConn)
} }
#[get("/users/<uuid>")] #[get("/users/<uuid>")]
async fn get_user_json(uuid: &str, _token: AdminToken, mut conn: DbConn) -> JsonResult { async fn get_user_json(uuid: &str, _token: AdminToken, conn: DbConn) -> JsonResult {
let u = get_user_or_404(uuid, &mut conn).await?; let u = get_user_or_404(uuid, &conn).await?;
let mut usr = u.to_json(&mut conn).await; let mut usr = u.to_json(&conn).await;
usr["UserEnabled"] = json!(u.enabled); usr["UserEnabled"] = json!(u.enabled);
usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); usr["CreatedAt"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT));
Ok(Json(usr)) Ok(Json(usr))
} }
#[post("/users/<uuid>/delete")] #[post("/users/<uuid>/delete")]
async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn delete_user(uuid: &str, token: AdminToken, conn: DbConn) -> EmptyResult {
let user = get_user_or_404(uuid, &mut conn).await?; let user = get_user_or_404(uuid, &conn).await?;
// Get the user_org records before deleting the actual user // Get the user_org records before deleting the actual user
let user_orgs = UserOrganization::find_any_state_by_user(uuid, &mut conn).await; let user_orgs = UserOrganization::find_any_state_by_user(uuid, &conn).await;
let res = user.delete(&mut conn).await; let res = user.delete(&conn).await;
for user_org in user_orgs { for user_org in user_orgs {
log_event( log_event(
@ -394,7 +401,7 @@ async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyRe
String::from(ACTING_ADMIN_USER), String::from(ACTING_ADMIN_USER),
14, // Use UnknownBrowser type 14, // Use UnknownBrowser type
&token.ip.ip, &token.ip.ip,
&mut conn, &conn,
) )
.await; .await;
} }
@ -403,13 +410,13 @@ async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyRe
} }
#[post("/users/<uuid>/deauth")] #[post("/users/<uuid>/deauth")]
async fn deauth_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn deauth_user(uuid: &str, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?; let mut user = get_user_or_404(uuid, &conn).await?;
nt.send_logout(&user, None).await; nt.send_logout(&user, None).await;
if CONFIG.push_enabled() { if CONFIG.push_enabled() {
for device in Device::find_push_devices_by_user(&user.uuid, &mut conn).await { for device in Device::find_push_devices_by_user(&user.uuid, &conn).await {
match unregister_push_device(device.uuid).await { match unregister_push_device(device.uuid).await {
Ok(r) => r, Ok(r) => r,
Err(e) => error!("Unable to unregister devices from Bitwarden server: {}", e), Err(e) => error!("Unable to unregister devices from Bitwarden server: {}", e),
@ -417,20 +424,20 @@ async fn deauth_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notif
} }
} }
Device::delete_all_by_user(&user.uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &conn).await?;
user.reset_security_stamp(); user.reset_security_stamp();
user.save(&mut conn).await user.save(&conn).await
} }
#[post("/users/<uuid>/disable")] #[post("/users/<uuid>/disable")]
async fn disable_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn disable_user(uuid: &str, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?; let mut user = get_user_or_404(uuid, &conn).await?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &conn).await?;
user.reset_security_stamp(); user.reset_security_stamp();
user.enabled = false; user.enabled = false;
let save_result = user.save(&mut conn).await; let save_result = user.save(&conn).await;
nt.send_logout(&user, None).await; nt.send_logout(&user, None).await;
@ -438,24 +445,24 @@ async fn disable_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Noti
} }
#[post("/users/<uuid>/enable")] #[post("/users/<uuid>/enable")]
async fn enable_user(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn enable_user(uuid: &str, _token: AdminToken, conn: DbConn) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?; let mut user = get_user_or_404(uuid, &conn).await?;
user.enabled = true; user.enabled = true;
user.save(&mut conn).await user.save(&conn).await
} }
#[post("/users/<uuid>/remove-2fa")] #[post("/users/<uuid>/remove-2fa")]
async fn remove_2fa(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn remove_2fa(uuid: &str, _token: AdminToken, conn: DbConn) -> EmptyResult {
let mut user = get_user_or_404(uuid, &mut conn).await?; let mut user = get_user_or_404(uuid, &conn).await?;
TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; TwoFactor::delete_all_by_user(&user.uuid, &conn).await?;
user.totp_recover = None; user.totp_recover = None;
user.save(&mut conn).await user.save(&conn).await
} }
#[post("/users/<uuid>/invite/resend")] #[post("/users/<uuid>/invite/resend")]
async fn resend_user_invite(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn resend_user_invite(uuid: &str, _token: AdminToken, conn: DbConn) -> EmptyResult {
if let Some(user) = User::find_by_uuid(uuid, &mut conn).await { if let Some(user) = User::find_by_uuid(uuid, &conn).await {
//TODO: replace this with user.status check when it will be available (PR#3397) //TODO: replace this with user.status check when it will be available (PR#3397)
if !user.password_hash.is_empty() { if !user.password_hash.is_empty() {
err_code!("User already accepted invitation", Status::BadRequest.code); err_code!("User already accepted invitation", Status::BadRequest.code);
@ -479,14 +486,13 @@ struct UserOrgTypeData {
} }
#[post("/users/org_type", data = "<data>")] #[post("/users/org_type", data = "<data>")]
async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, conn: DbConn) -> EmptyResult {
let data: UserOrgTypeData = data.into_inner(); let data: UserOrgTypeData = data.into_inner();
let mut user_to_edit = let mut user_to_edit = match UserOrganization::find_by_user_and_org(&data.user_uuid, &data.org_uuid, &conn).await {
match UserOrganization::find_by_user_and_org(&data.user_uuid, &data.org_uuid, &mut conn).await { Some(user) => user,
Some(user) => user, None => err!("The specified user isn't member of the organization"),
None => err!("The specified user isn't member of the organization"), };
};
let new_type = match UserOrgType::from_str(&data.user_type.into_string()) { let new_type = match UserOrgType::from_str(&data.user_type.into_string()) {
Some(new_type) => new_type as i32, Some(new_type) => new_type as i32,
@ -495,7 +501,7 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mu
if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner { if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner {
// Removing owner permission, check that there is at least one other confirmed owner // Removing owner permission, check that there is at least one other confirmed owner
if UserOrganization::count_confirmed_by_org_and_type(&data.org_uuid, UserOrgType::Owner, &mut conn).await <= 1 { if UserOrganization::count_confirmed_by_org_and_type(&data.org_uuid, UserOrgType::Owner, &conn).await <= 1 {
err!("Can't change the type of the last owner") err!("Can't change the type of the last owner")
} }
} }
@ -503,7 +509,7 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mu
// This check is also done at api::organizations::{accept_invite(), _confirm_invite, _activate_user(), edit_user()}, update_user_org_type // This check is also done at api::organizations::{accept_invite(), _confirm_invite, _activate_user(), edit_user()}, update_user_org_type
// It returns different error messages per function. // It returns different error messages per function.
if new_type < UserOrgType::Admin { if new_type < UserOrgType::Admin {
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, &user_to_edit.org_uuid, true, &mut conn).await { match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, &user_to_edit.org_uuid, true, &conn).await {
Ok(_) => {} Ok(_) => {}
Err(OrgPolicyErr::TwoFactorMissing) => { Err(OrgPolicyErr::TwoFactorMissing) => {
err!("You cannot modify this user to this type because it has no two-step login method activated"); err!("You cannot modify this user to this type because it has no two-step login method activated");
@ -521,32 +527,32 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, token: AdminToken, mu
String::from(ACTING_ADMIN_USER), String::from(ACTING_ADMIN_USER),
14, // Use UnknownBrowser type 14, // Use UnknownBrowser type
&token.ip.ip, &token.ip.ip,
&mut conn, &conn,
) )
.await; .await;
user_to_edit.atype = new_type; user_to_edit.atype = new_type;
user_to_edit.save(&mut conn).await user_to_edit.save(&conn).await
} }
#[post("/users/update_revision")] #[post("/users/update_revision")]
async fn update_revision_users(_token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
User::update_all_revisions(&mut conn).await User::update_all_revisions(&conn).await
} }
#[get("/organizations/overview")] #[get("/organizations/overview")]
async fn organizations_overview(_token: AdminToken, mut conn: DbConn) -> ApiResult<Html<String>> { async fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
let organizations = Organization::get_all(&mut conn).await; let organizations = Organization::get_all(&conn).await;
let mut organizations_json = Vec::with_capacity(organizations.len()); let mut organizations_json = Vec::with_capacity(organizations.len());
for o in organizations { for o in organizations {
let mut org = o.to_json(); let mut org = o.to_json();
org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &mut conn).await); org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &conn).await);
org["cipher_count"] = json!(Cipher::count_by_org(&o.uuid, &mut conn).await); org["cipher_count"] = json!(Cipher::count_by_org(&o.uuid, &conn).await);
org["collection_count"] = json!(Collection::count_by_org(&o.uuid, &mut conn).await); org["collection_count"] = json!(Collection::count_by_org(&o.uuid, &conn).await);
org["group_count"] = json!(Group::count_by_org(&o.uuid, &mut conn).await); org["group_count"] = json!(Group::count_by_org(&o.uuid, &conn).await);
org["event_count"] = json!(Event::count_by_org(&o.uuid, &mut conn).await); org["event_count"] = json!(Event::count_by_org(&o.uuid, &conn).await);
org["attachment_count"] = json!(Attachment::count_by_org(&o.uuid, &mut conn).await); org["attachment_count"] = json!(Attachment::count_by_org(&o.uuid, &conn).await);
org["attachment_size"] = json!(get_display_size(Attachment::size_by_org(&o.uuid, &mut conn).await as i32)); org["attachment_size"] = json!(get_display_size(Attachment::size_by_org(&o.uuid, &conn).await as i32));
organizations_json.push(org); organizations_json.push(org);
} }
@ -555,9 +561,9 @@ async fn organizations_overview(_token: AdminToken, mut conn: DbConn) -> ApiResu
} }
#[post("/organizations/<uuid>/delete")] #[post("/organizations/<uuid>/delete")]
async fn delete_organization(uuid: &str, _token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn delete_organization(uuid: &str, _token: AdminToken, conn: DbConn) -> EmptyResult {
let org = Organization::find_by_uuid(uuid, &mut conn).await.map_res("Organization doesn't exist")?; let org = Organization::find_by_uuid(uuid, &conn).await.map_res("Organization doesn't exist")?;
org.delete(&mut conn).await org.delete(&conn).await
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -660,7 +666,7 @@ async fn get_ntp_time(has_http_access: bool) -> String {
} }
#[get("/diagnostics")] #[get("/diagnostics")]
async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn) -> ApiResult<Html<String>> { async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult<Html<String>> {
use chrono::prelude::*; use chrono::prelude::*;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
@ -715,7 +721,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn)
"ip_header_config": &CONFIG.ip_header(), "ip_header_config": &CONFIG.ip_header(),
"uses_proxy": uses_proxy, "uses_proxy": uses_proxy,
"db_type": *DB_TYPE, "db_type": *DB_TYPE,
"db_version": get_sql_server_version(&mut conn).await, "db_version": get_sql_server_version(&conn).await,
"admin_url": format!("{}/diagnostics", admin_url()), "admin_url": format!("{}/diagnostics", admin_url()),
"overrides": &CONFIG.get_overrides().join(", "), "overrides": &CONFIG.get_overrides().join(", "),
"host_arch": std::env::consts::ARCH, "host_arch": std::env::consts::ARCH,
@ -747,9 +753,9 @@ fn delete_config(_token: AdminToken) -> EmptyResult {
} }
#[post("/config/backup_db")] #[post("/config/backup_db")]
async fn backup_db(_token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn backup_db(_token: AdminToken, conn: DbConn) -> EmptyResult {
if *CAN_BACKUP { if *CAN_BACKUP {
backup_database(&mut conn).await backup_database(&conn).await
} else { } else {
err!("Can't back up current DB (Only SQLite supports this feature)"); err!("Can't back up current DB (Only SQLite supports this feature)");
} }

Datei anzeigen

@ -108,7 +108,7 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult {
_register(data, conn).await _register(data, conn).await
} }
pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> JsonResult { pub async fn _register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult {
let data: RegisterData = data.into_inner().data; let data: RegisterData = data.into_inner().data;
let email = data.Email.to_lowercase(); let email = data.Email.to_lowercase();
@ -127,7 +127,7 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
let mut verified_by_invite = false; let mut verified_by_invite = false;
let mut user = match User::find_by_mail(&email, &mut conn).await { let mut user = match User::find_by_mail(&email, &conn).await {
Some(mut user) => { Some(mut user) => {
if !user.password_hash.is_empty() { if !user.password_hash.is_empty() {
err!("Registration not allowed or user already exists") err!("Registration not allowed or user already exists")
@ -143,14 +143,14 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
} else { } else {
err!("Registration email does not match invite email") err!("Registration email does not match invite email")
} }
} else if Invitation::take(&email, &mut conn).await { } else if Invitation::take(&email, &conn).await {
for user_org in UserOrganization::find_invited_by_user(&user.uuid, &mut conn).await.iter_mut() { for user_org in UserOrganization::find_invited_by_user(&user.uuid, &conn).await.iter_mut() {
user_org.status = UserOrgStatus::Accepted as i32; user_org.status = UserOrgStatus::Accepted as i32;
user_org.save(&mut conn).await?; user_org.save(&conn).await?;
} }
user user
} else if CONFIG.is_signup_allowed(&email) } else if CONFIG.is_signup_allowed(&email)
|| EmergencyAccess::find_invited_by_grantee_email(&email, &mut conn).await.is_some() || EmergencyAccess::find_invited_by_grantee_email(&email, &conn).await.is_some()
{ {
user user
} else { } else {
@ -161,7 +161,7 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
// Order is important here; the invitation check must come first // Order is important here; the invitation check must come first
// because the vaultwarden admin can invite anyone, regardless // because the vaultwarden admin can invite anyone, regardless
// of other signup restrictions. // of other signup restrictions.
if Invitation::take(&email, &mut conn).await || CONFIG.is_signup_allowed(&email) { if Invitation::take(&email, &conn).await || CONFIG.is_signup_allowed(&email) {
User::new(email.clone()) User::new(email.clone())
} else { } else {
err!("Registration not allowed or user already exists") err!("Registration not allowed or user already exists")
@ -170,7 +170,7 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
}; };
// Make sure we don't leave a lingering invitation. // Make sure we don't leave a lingering invitation.
Invitation::take(&email, &mut conn).await; Invitation::take(&email, &conn).await;
if let Some(client_kdf_type) = data.Kdf { if let Some(client_kdf_type) = data.Kdf {
user.client_kdf_type = client_kdf_type; user.client_kdf_type = client_kdf_type;
@ -208,7 +208,7 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
} }
} }
user.save(&mut conn).await?; user.save(&conn).await?;
Ok(Json(json!({ Ok(Json(json!({
"Object": "register", "Object": "register",
"CaptchaBypassToken": "", "CaptchaBypassToken": "",
@ -216,8 +216,8 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
} }
#[get("/accounts/profile")] #[get("/accounts/profile")]
async fn profile(headers: Headers, mut conn: DbConn) -> Json<Value> { async fn profile(headers: Headers, conn: DbConn) -> Json<Value> {
Json(headers.user.to_json(&mut conn).await) Json(headers.user.to_json(&conn).await)
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -234,7 +234,7 @@ async fn put_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbCo
} }
#[post("/accounts/profile", data = "<data>")] #[post("/accounts/profile", data = "<data>")]
async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: ProfileData = data.into_inner().data; let data: ProfileData = data.into_inner().data;
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden) // Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
@ -246,8 +246,8 @@ async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, mut conn:
let mut user = headers.user; let mut user = headers.user;
user.name = data.Name; user.name = data.Name;
user.save(&mut conn).await?; user.save(&conn).await?;
Ok(Json(user.to_json(&mut conn).await)) Ok(Json(user.to_json(&conn).await))
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -257,7 +257,7 @@ struct AvatarData {
} }
#[put("/accounts/avatar", data = "<data>")] #[put("/accounts/avatar", data = "<data>")]
async fn put_avatar(data: JsonUpcase<AvatarData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn put_avatar(data: JsonUpcase<AvatarData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: AvatarData = data.into_inner().data; let data: AvatarData = data.into_inner().data;
// It looks like it only supports the 6 hex color format. // It looks like it only supports the 6 hex color format.
@ -272,13 +272,13 @@ async fn put_avatar(data: JsonUpcase<AvatarData>, headers: Headers, mut conn: Db
let mut user = headers.user; let mut user = headers.user;
user.avatar_color = data.AvatarColor; user.avatar_color = data.AvatarColor;
user.save(&mut conn).await?; user.save(&conn).await?;
Ok(Json(user.to_json(&mut conn).await)) Ok(Json(user.to_json(&conn).await))
} }
#[get("/users/<uuid>/public-key")] #[get("/users/<uuid>/public-key")]
async fn get_public_keys(uuid: &str, _headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_public_keys(uuid: &str, _headers: Headers, conn: DbConn) -> JsonResult {
let user = match User::find_by_uuid(uuid, &mut conn).await { let user = match User::find_by_uuid(uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("User doesn't exist"), None => err!("User doesn't exist"),
}; };
@ -291,7 +291,7 @@ async fn get_public_keys(uuid: &str, _headers: Headers, mut conn: DbConn) -> Jso
} }
#[post("/accounts/keys", data = "<data>")] #[post("/accounts/keys", data = "<data>")]
async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: KeysData = data.into_inner().data; let data: KeysData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -299,7 +299,7 @@ async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, mut conn: DbCon
user.private_key = Some(data.EncryptedPrivateKey); user.private_key = Some(data.EncryptedPrivateKey);
user.public_key = Some(data.PublicKey); user.public_key = Some(data.PublicKey);
user.save(&mut conn).await?; user.save(&conn).await?;
Ok(Json(json!({ Ok(Json(json!({
"PrivateKey": user.private_key, "PrivateKey": user.private_key,
@ -321,7 +321,7 @@ struct ChangePassData {
async fn post_password( async fn post_password(
data: JsonUpcase<ChangePassData>, data: JsonUpcase<ChangePassData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let data: ChangePassData = data.into_inner().data; let data: ChangePassData = data.into_inner().data;
@ -334,7 +334,7 @@ async fn post_password(
user.password_hint = clean_password_hint(&data.MasterPasswordHint); user.password_hint = clean_password_hint(&data.MasterPasswordHint);
enforce_password_hint_setting(&user.password_hint)?; enforce_password_hint_setting(&user.password_hint)?;
log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn) log_user_event(EventType::UserChangedPassword as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn)
.await; .await;
user.set_password( user.set_password(
@ -344,7 +344,7 @@ async fn post_password(
Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]), Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]),
); );
let save_result = user.save(&mut conn).await; let save_result = user.save(&conn).await;
// Prevent logging out the client where the user requested this endpoint from. // Prevent logging out the client where the user requested this endpoint from.
// If you do logout the user it will causes issues at the client side. // If you do logout the user it will causes issues at the client side.
@ -368,7 +368,7 @@ struct ChangeKdfData {
} }
#[post("/accounts/kdf", data = "<data>")] #[post("/accounts/kdf", data = "<data>")]
async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let data: ChangeKdfData = data.into_inner().data; let data: ChangeKdfData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -407,7 +407,7 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
user.client_kdf_iter = data.KdfIterations; user.client_kdf_iter = data.KdfIterations;
user.client_kdf_type = data.Kdf; user.client_kdf_type = data.Kdf;
user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None); user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None);
let save_result = user.save(&mut conn).await; let save_result = user.save(&conn).await;
nt.send_logout(&user, Some(headers.device.uuid)).await; nt.send_logout(&user, Some(headers.device.uuid)).await;
@ -434,7 +434,7 @@ struct KeyData {
} }
#[post("/accounts/key", data = "<data>")] #[post("/accounts/key", data = "<data>")]
async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let data: KeyData = data.into_inner().data; let data: KeyData = data.into_inner().data;
if !headers.user.check_valid_password(&data.MasterPasswordHash) { if !headers.user.check_valid_password(&data.MasterPasswordHash) {
@ -451,7 +451,7 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
// Update folder data // Update folder data
for folder_data in data.Folders { for folder_data in data.Folders {
let mut saved_folder = match Folder::find_by_uuid(&folder_data.Id, &mut conn).await { let mut saved_folder = match Folder::find_by_uuid(&folder_data.Id, &conn).await {
Some(folder) => folder, Some(folder) => folder,
None => err!("Folder doesn't exist"), None => err!("Folder doesn't exist"),
}; };
@ -461,14 +461,14 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
} }
saved_folder.name = folder_data.Name; saved_folder.name = folder_data.Name;
saved_folder.save(&mut conn).await? saved_folder.save(&conn).await?
} }
// Update cipher data // Update cipher data
use super::ciphers::update_cipher_from_data; use super::ciphers::update_cipher_from_data;
for cipher_data in data.Ciphers { for cipher_data in data.Ciphers {
let mut saved_cipher = match Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &mut conn).await { let mut saved_cipher = match Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
}; };
@ -480,8 +480,7 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
// Prevent triggering cipher updates via WebSockets by settings UpdateType::None // Prevent triggering cipher updates via WebSockets by settings UpdateType::None
// The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues. // The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues.
// We force the users to logout after the user has been saved to try and prevent these issues. // We force the users to logout after the user has been saved to try and prevent these issues.
update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &mut conn, &nt, UpdateType::None) update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None).await?
.await?
} }
// Update user data // Update user data
@ -491,7 +490,7 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
user.private_key = Some(data.PrivateKey); user.private_key = Some(data.PrivateKey);
user.reset_security_stamp(); user.reset_security_stamp();
let save_result = user.save(&mut conn).await; let save_result = user.save(&conn).await;
// Prevent logging out the client where the user requested this endpoint from. // Prevent logging out the client where the user requested this endpoint from.
// If you do logout the user it will causes issues at the client side. // If you do logout the user it will causes issues at the client side.
@ -502,12 +501,7 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
} }
#[post("/accounts/security-stamp", data = "<data>")] #[post("/accounts/security-stamp", data = "<data>")]
async fn post_sstamp( async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
data: JsonUpcase<PasswordData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
let data: PasswordData = data.into_inner().data; let data: PasswordData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -515,9 +509,9 @@ async fn post_sstamp(
err!("Invalid password") err!("Invalid password")
} }
Device::delete_all_by_user(&user.uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &conn).await?;
user.reset_security_stamp(); user.reset_security_stamp();
let save_result = user.save(&mut conn).await; let save_result = user.save(&conn).await;
nt.send_logout(&user, None).await; nt.send_logout(&user, None).await;
@ -532,7 +526,7 @@ struct EmailTokenData {
} }
#[post("/accounts/email-token", data = "<data>")] #[post("/accounts/email-token", data = "<data>")]
async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: DbConn) -> EmptyResult {
if !CONFIG.email_change_allowed() { if !CONFIG.email_change_allowed() {
err!("Email change is not allowed."); err!("Email change is not allowed.");
} }
@ -544,7 +538,7 @@ async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, mu
err!("Invalid password") err!("Invalid password")
} }
if User::find_by_mail(&data.NewEmail, &mut conn).await.is_some() { if User::find_by_mail(&data.NewEmail, &conn).await.is_some() {
err!("Email already in use"); err!("Email already in use");
} }
@ -562,7 +556,7 @@ async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, mu
user.email_new = Some(data.NewEmail); user.email_new = Some(data.NewEmail);
user.email_new_token = Some(token); user.email_new_token = Some(token);
user.save(&mut conn).await user.save(&conn).await
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -577,12 +571,7 @@ struct ChangeEmailData {
} }
#[post("/accounts/email", data = "<data>")] #[post("/accounts/email", data = "<data>")]
async fn post_email( async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
data: JsonUpcase<ChangeEmailData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
if !CONFIG.email_change_allowed() { if !CONFIG.email_change_allowed() {
err!("Email change is not allowed."); err!("Email change is not allowed.");
} }
@ -594,7 +583,7 @@ async fn post_email(
err!("Invalid password") err!("Invalid password")
} }
if User::find_by_mail(&data.NewEmail, &mut conn).await.is_some() { if User::find_by_mail(&data.NewEmail, &conn).await.is_some() {
err!("Email already in use"); err!("Email already in use");
} }
@ -628,7 +617,7 @@ async fn post_email(
user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None); user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None);
let save_result = user.save(&mut conn).await; let save_result = user.save(&conn).await;
nt.send_logout(&user, None).await; nt.send_logout(&user, None).await;
@ -658,10 +647,10 @@ struct VerifyEmailTokenData {
} }
#[post("/accounts/verify-email-token", data = "<data>")] #[post("/accounts/verify-email-token", data = "<data>")]
async fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, mut conn: DbConn) -> EmptyResult { async fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: DbConn) -> EmptyResult {
let data: VerifyEmailTokenData = data.into_inner().data; let data: VerifyEmailTokenData = data.into_inner().data;
let mut user = match User::find_by_uuid(&data.UserId, &mut conn).await { let mut user = match User::find_by_uuid(&data.UserId, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("User doesn't exist"), None => err!("User doesn't exist"),
}; };
@ -676,7 +665,7 @@ async fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, mut con
user.verified_at = Some(Utc::now().naive_utc()); user.verified_at = Some(Utc::now().naive_utc());
user.last_verifying_at = None; user.last_verifying_at = None;
user.login_verify_count = 0; user.login_verify_count = 0;
if let Err(e) = user.save(&mut conn).await { if let Err(e) = user.save(&conn).await {
error!("Error saving email verification: {:#?}", e); error!("Error saving email verification: {:#?}", e);
} }
@ -690,11 +679,11 @@ struct DeleteRecoverData {
} }
#[post("/accounts/delete-recover", data = "<data>")] #[post("/accounts/delete-recover", data = "<data>")]
async fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, mut conn: DbConn) -> EmptyResult { async fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, conn: DbConn) -> EmptyResult {
let data: DeleteRecoverData = data.into_inner().data; let data: DeleteRecoverData = data.into_inner().data;
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
if let Some(user) = User::find_by_mail(&data.Email, &mut conn).await { if let Some(user) = User::find_by_mail(&data.Email, &conn).await {
if let Err(e) = mail::send_delete_account(&user.email, &user.uuid).await { if let Err(e) = mail::send_delete_account(&user.email, &user.uuid).await {
error!("Error sending delete account email: {:#?}", e); error!("Error sending delete account email: {:#?}", e);
} }
@ -717,10 +706,10 @@ struct DeleteRecoverTokenData {
} }
#[post("/accounts/delete-recover-token", data = "<data>")] #[post("/accounts/delete-recover-token", data = "<data>")]
async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, mut conn: DbConn) -> EmptyResult { async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, conn: DbConn) -> EmptyResult {
let data: DeleteRecoverTokenData = data.into_inner().data; let data: DeleteRecoverTokenData = data.into_inner().data;
let user = match User::find_by_uuid(&data.UserId, &mut conn).await { let user = match User::find_by_uuid(&data.UserId, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("User doesn't exist"), None => err!("User doesn't exist"),
}; };
@ -732,7 +721,7 @@ async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, mut
if claims.sub != user.uuid { if claims.sub != user.uuid {
err!("Invalid claim"); err!("Invalid claim");
} }
user.delete(&mut conn).await user.delete(&conn).await
} }
#[post("/accounts/delete", data = "<data>")] #[post("/accounts/delete", data = "<data>")]
@ -741,7 +730,7 @@ async fn post_delete_account(data: JsonUpcase<PasswordData>, headers: Headers, c
} }
#[delete("/accounts", data = "<data>")] #[delete("/accounts", data = "<data>")]
async fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: PasswordData = data.into_inner().data; let data: PasswordData = data.into_inner().data;
let user = headers.user; let user = headers.user;
@ -749,7 +738,7 @@ async fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, mut co
err!("Invalid password") err!("Invalid password")
} }
user.delete(&mut conn).await user.delete(&conn).await
} }
#[get("/accounts/revision-date")] #[get("/accounts/revision-date")]
@ -765,7 +754,7 @@ struct PasswordHintData {
} }
#[post("/accounts/password-hint", data = "<data>")] #[post("/accounts/password-hint", data = "<data>")]
async fn password_hint(data: JsonUpcase<PasswordHintData>, mut conn: DbConn) -> EmptyResult { async fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult {
if !CONFIG.mail_enabled() && !CONFIG.show_password_hint() { if !CONFIG.mail_enabled() && !CONFIG.show_password_hint() {
err!("This server is not configured to provide password hints."); err!("This server is not configured to provide password hints.");
} }
@ -775,7 +764,7 @@ async fn password_hint(data: JsonUpcase<PasswordHintData>, mut conn: DbConn) ->
let data: PasswordHintData = data.into_inner().data; let data: PasswordHintData = data.into_inner().data;
let email = &data.Email; let email = &data.Email;
match User::find_by_mail(email, &mut conn).await { match User::find_by_mail(email, &conn).await {
None => { None => {
// To prevent user enumeration, act as if the user exists. // To prevent user enumeration, act as if the user exists.
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
@ -817,10 +806,10 @@ async fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> {
_prelogin(data, conn).await _prelogin(data, conn).await
} }
pub async fn _prelogin(data: JsonUpcase<PreloginData>, mut conn: DbConn) -> Json<Value> { pub async fn _prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> {
let data: PreloginData = data.into_inner().data; let data: PreloginData = data.into_inner().data;
let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.Email, &mut conn).await { let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.Email, &conn).await {
Some(user) => (user.client_kdf_type, user.client_kdf_iter, user.client_kdf_memory, user.client_kdf_parallelism), Some(user) => (user.client_kdf_type, user.client_kdf_iter, user.client_kdf_memory, user.client_kdf_parallelism),
None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None), None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None),
}; };
@ -858,7 +847,7 @@ async fn _api_key(
data: JsonUpcase<SecretVerificationRequest>, data: JsonUpcase<SecretVerificationRequest>,
rotate: bool, rotate: bool,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
) -> JsonResult { ) -> JsonResult {
use crate::util::format_date; use crate::util::format_date;
@ -871,7 +860,7 @@ async fn _api_key(
if rotate || user.api_key.is_none() { if rotate || user.api_key.is_none() {
user.api_key = Some(crypto::generate_api_key()); user.api_key = Some(crypto::generate_api_key());
user.save(&mut conn).await.expect("Error saving API key"); user.save(&conn).await.expect("Error saving API key");
} }
Ok(Json(json!({ Ok(Json(json!({
@ -893,11 +882,11 @@ async fn rotate_api_key(data: JsonUpcase<SecretVerificationRequest>, headers: He
// This variant is deprecated: https://github.com/bitwarden/server/pull/2682 // This variant is deprecated: https://github.com/bitwarden/server/pull/2682
#[get("/devices/knowndevice/<email>/<uuid>")] #[get("/devices/knowndevice/<email>/<uuid>")]
async fn get_known_device_from_path(email: &str, uuid: &str, mut conn: DbConn) -> JsonResult { async fn get_known_device_from_path(email: &str, uuid: &str, conn: DbConn) -> JsonResult {
// This endpoint doesn't have auth header // This endpoint doesn't have auth header
let mut result = false; let mut result = false;
if let Some(user) = User::find_by_mail(email, &mut conn).await { if let Some(user) = User::find_by_mail(email, &conn).await {
result = Device::find_by_uuid_and_user(uuid, &user.uuid, &mut conn).await.is_some(); result = Device::find_by_uuid_and_user(uuid, &user.uuid, &conn).await.is_some();
} }
Ok(Json(json!(result))) Ok(Json(json!(result)))
} }
@ -962,14 +951,14 @@ async fn post_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Hea
} }
#[put("/devices/identifier/<uuid>/token", data = "<data>")] #[put("/devices/identifier/<uuid>/token", data = "<data>")]
async fn put_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn put_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Headers, conn: DbConn) -> EmptyResult {
if !CONFIG.push_enabled() { if !CONFIG.push_enabled() {
return Ok(()); return Ok(());
} }
let data = data.into_inner().data; let data = data.into_inner().data;
let token = data.PushToken; let token = data.PushToken;
let mut device = match Device::find_by_uuid_and_user(&headers.device.uuid, &headers.user.uuid, &mut conn).await { let mut device = match Device::find_by_uuid_and_user(&headers.device.uuid, &headers.user.uuid, &conn).await {
Some(device) => device, Some(device) => device,
None => err!(format!("Error: device {uuid} should be present before a token can be assigned")), None => err!(format!("Error: device {uuid} should be present before a token can be assigned")),
}; };
@ -977,7 +966,7 @@ async fn put_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Head
if device.push_uuid.is_none() { if device.push_uuid.is_none() {
device.push_uuid = Some(uuid::Uuid::new_v4().to_string()); device.push_uuid = Some(uuid::Uuid::new_v4().to_string());
} }
if let Err(e) = device.save(&mut conn).await { if let Err(e) = device.save(&conn).await {
err!(format!("An error occurred while trying to save the device push token: {e}")); err!(format!("An error occurred while trying to save the device push token: {e}"));
} }
if let Err(e) = register_push_device(headers.user.uuid, device).await { if let Err(e) = register_push_device(headers.user.uuid, device).await {
@ -988,7 +977,7 @@ async fn put_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Head
} }
#[put("/devices/identifier/<uuid>/clear-token")] #[put("/devices/identifier/<uuid>/clear-token")]
async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult { async fn put_clear_device_token(uuid: &str, conn: DbConn) -> EmptyResult {
// This only clears push token // This only clears push token
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109 // https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37 // https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
@ -997,8 +986,8 @@ async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult {
return Ok(()); return Ok(());
} }
if let Some(device) = Device::find_by_uuid(uuid, &mut conn).await { if let Some(device) = Device::find_by_uuid(uuid, &conn).await {
Device::clear_push_token_by_uuid(uuid, &mut conn).await?; Device::clear_push_token_by_uuid(uuid, &conn).await?;
unregister_push_device(device.uuid).await?; unregister_push_device(device.uuid).await?;
} }
@ -1026,12 +1015,12 @@ struct AuthRequestRequest {
async fn post_auth_request( async fn post_auth_request(
data: Json<AuthRequestRequest>, data: Json<AuthRequestRequest>,
headers: ClientHeaders, headers: ClientHeaders,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let data = data.into_inner(); let data = data.into_inner();
let user = match User::find_by_mail(&data.email, &mut conn).await { let user = match User::find_by_mail(&data.email, &conn).await {
Some(user) => user, Some(user) => user,
None => { None => {
err!("AuthRequest doesn't exist") err!("AuthRequest doesn't exist")
@ -1046,9 +1035,9 @@ async fn post_auth_request(
data.accessCode, data.accessCode,
data.publicKey, data.publicKey,
); );
auth_request.save(&mut conn).await?; auth_request.save(&conn).await?;
nt.send_auth_request(&user.uuid, &auth_request.uuid, &data.deviceIdentifier, &mut conn).await; nt.send_auth_request(&user.uuid, &auth_request.uuid, &data.deviceIdentifier, &conn).await;
Ok(Json(json!({ Ok(Json(json!({
"id": auth_request.uuid, "id": auth_request.uuid,
@ -1066,8 +1055,8 @@ async fn post_auth_request(
} }
#[get("/auth-requests/<uuid>")] #[get("/auth-requests/<uuid>")]
async fn get_auth_request(uuid: &str, mut conn: DbConn) -> JsonResult { async fn get_auth_request(uuid: &str, conn: DbConn) -> JsonResult {
let auth_request = match AuthRequest::find_by_uuid(uuid, &mut conn).await { let auth_request = match AuthRequest::find_by_uuid(uuid, &conn).await {
Some(auth_request) => auth_request, Some(auth_request) => auth_request,
None => { None => {
err!("AuthRequest doesn't exist") err!("AuthRequest doesn't exist")
@ -1106,12 +1095,12 @@ struct AuthResponseRequest {
async fn put_auth_request( async fn put_auth_request(
uuid: &str, uuid: &str,
data: Json<AuthResponseRequest>, data: Json<AuthResponseRequest>,
mut conn: DbConn, conn: DbConn,
ant: AnonymousNotify<'_>, ant: AnonymousNotify<'_>,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let data = data.into_inner(); let data = data.into_inner();
let mut auth_request: AuthRequest = match AuthRequest::find_by_uuid(uuid, &mut conn).await { let mut auth_request: AuthRequest = match AuthRequest::find_by_uuid(uuid, &conn).await {
Some(auth_request) => auth_request, Some(auth_request) => auth_request,
None => { None => {
err!("AuthRequest doesn't exist") err!("AuthRequest doesn't exist")
@ -1122,11 +1111,11 @@ async fn put_auth_request(
auth_request.enc_key = Some(data.key); auth_request.enc_key = Some(data.key);
auth_request.master_password_hash = data.masterPasswordHash; auth_request.master_password_hash = data.masterPasswordHash;
auth_request.response_device_id = Some(data.deviceIdentifier.clone()); auth_request.response_device_id = Some(data.deviceIdentifier.clone());
auth_request.save(&mut conn).await?; auth_request.save(&conn).await?;
if auth_request.approved.unwrap_or(false) { if auth_request.approved.unwrap_or(false) {
ant.send_auth_response(&auth_request.user_uuid, &auth_request.uuid).await; ant.send_auth_response(&auth_request.user_uuid, &auth_request.uuid).await;
nt.send_auth_response(&auth_request.user_uuid, &auth_request.uuid, data.deviceIdentifier, &mut conn).await; nt.send_auth_response(&auth_request.user_uuid, &auth_request.uuid, data.deviceIdentifier, &conn).await;
} }
let response_date_utc = auth_request.response_date.map(|response_date| response_date.and_utc()); let response_date_utc = auth_request.response_date.map(|response_date| response_date.and_utc());
@ -1149,8 +1138,8 @@ async fn put_auth_request(
} }
#[get("/auth-requests/<uuid>/response?<code>")] #[get("/auth-requests/<uuid>/response?<code>")]
async fn get_auth_request_response(uuid: &str, code: &str, mut conn: DbConn) -> JsonResult { async fn get_auth_request_response(uuid: &str, code: &str, conn: DbConn) -> JsonResult {
let auth_request = match AuthRequest::find_by_uuid(uuid, &mut conn).await { let auth_request = match AuthRequest::find_by_uuid(uuid, &conn).await {
Some(auth_request) => auth_request, Some(auth_request) => auth_request,
None => { None => {
err!("AuthRequest doesn't exist") err!("AuthRequest doesn't exist")
@ -1181,8 +1170,8 @@ async fn get_auth_request_response(uuid: &str, code: &str, mut conn: DbConn) ->
} }
#[get("/auth-requests")] #[get("/auth-requests")]
async fn get_auth_requests(headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_auth_requests(headers: Headers, conn: DbConn) -> JsonResult {
let auth_requests = AuthRequest::find_by_user(&headers.user.uuid, &mut conn).await; let auth_requests = AuthRequest::find_by_user(&headers.user.uuid, &conn).await;
Ok(Json(json!({ Ok(Json(json!({
"data": auth_requests "data": auth_requests
@ -1212,8 +1201,8 @@ async fn get_auth_requests(headers: Headers, mut conn: DbConn) -> JsonResult {
pub async fn purge_auth_requests(pool: DbPool) { pub async fn purge_auth_requests(pool: DbPool) {
debug!("Purging auth requests"); debug!("Purging auth requests");
if let Ok(mut conn) = pool.get().await { if let Ok(conn) = pool.get().await {
AuthRequest::purge_expired_auth_requests(&mut conn).await; AuthRequest::purge_expired_auth_requests(&conn).await;
} else { } else {
error!("Failed to get DB connection while purging trashed ciphers") error!("Failed to get DB connection while purging trashed ciphers")
} }

Datei anzeigen

@ -86,8 +86,8 @@ pub fn routes() -> Vec<Route> {
pub async fn purge_trashed_ciphers(pool: DbPool) { pub async fn purge_trashed_ciphers(pool: DbPool) {
debug!("Purging trashed ciphers"); debug!("Purging trashed ciphers");
if let Ok(mut conn) = pool.get().await { if let Ok(conn) = pool.get().await {
Cipher::purge_trash(&mut conn).await; Cipher::purge_trash(&conn).await;
} else { } else {
error!("Failed to get DB connection while purging trashed ciphers") error!("Failed to get DB connection while purging trashed ciphers")
} }
@ -100,37 +100,36 @@ struct SyncData {
} }
#[get("/sync?<data..>")] #[get("/sync?<data..>")]
async fn sync(data: SyncData, headers: Headers, mut conn: DbConn) -> Json<Value> { async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> {
let user_json = headers.user.to_json(&mut conn).await; let user_json = headers.user.to_json(&conn).await;
// Get all ciphers which are visible by the user // Get all ciphers which are visible by the user
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &mut conn).await; let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn).await;
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, CipherSyncType::User, &mut conn).await; let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, CipherSyncType::User, &conn).await;
// Lets generate the ciphers_json using all the gathered info // Lets generate the ciphers_json using all the gathered info
let mut ciphers_json = Vec::with_capacity(ciphers.len()); let mut ciphers_json = Vec::with_capacity(ciphers.len());
for c in ciphers { for c in ciphers {
ciphers_json.push( ciphers_json.push(
c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), CipherSyncType::User, &mut conn) c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), CipherSyncType::User, &conn).await,
.await,
); );
} }
let collections = Collection::find_by_user_uuid(headers.user.uuid.clone(), &mut conn).await; let collections = Collection::find_by_user_uuid(headers.user.uuid.clone(), &conn).await;
let mut collections_json = Vec::with_capacity(collections.len()); let mut collections_json = Vec::with_capacity(collections.len());
for c in collections { for c in collections {
collections_json.push(c.to_json_details(&headers.user.uuid, Some(&cipher_sync_data), &mut conn).await); collections_json.push(c.to_json_details(&headers.user.uuid, Some(&cipher_sync_data), &conn).await);
} }
let folders_json: Vec<Value> = let folders_json: Vec<Value> =
Folder::find_by_user(&headers.user.uuid, &mut conn).await.iter().map(Folder::to_json).collect(); Folder::find_by_user(&headers.user.uuid, &conn).await.iter().map(Folder::to_json).collect();
let sends_json: Vec<Value> = let sends_json: Vec<Value> =
Send::find_by_user(&headers.user.uuid, &mut conn).await.iter().map(Send::to_json).collect(); Send::find_by_user(&headers.user.uuid, &conn).await.iter().map(Send::to_json).collect();
let policies_json: Vec<Value> = let policies_json: Vec<Value> =
OrgPolicy::find_confirmed_by_user(&headers.user.uuid, &mut conn).await.iter().map(OrgPolicy::to_json).collect(); OrgPolicy::find_confirmed_by_user(&headers.user.uuid, &conn).await.iter().map(OrgPolicy::to_json).collect();
let domains_json = if data.exclude_domains { let domains_json = if data.exclude_domains {
Value::Null Value::Null
@ -152,15 +151,14 @@ async fn sync(data: SyncData, headers: Headers, mut conn: DbConn) -> Json<Value>
} }
#[get("/ciphers")] #[get("/ciphers")]
async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> { async fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> {
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &mut conn).await; let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn).await;
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, CipherSyncType::User, &mut conn).await; let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, CipherSyncType::User, &conn).await;
let mut ciphers_json = Vec::with_capacity(ciphers.len()); let mut ciphers_json = Vec::with_capacity(ciphers.len());
for c in ciphers { for c in ciphers {
ciphers_json.push( ciphers_json.push(
c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), CipherSyncType::User, &mut conn) c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), CipherSyncType::User, &conn).await,
.await,
); );
} }
@ -172,17 +170,17 @@ async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> {
} }
#[get("/ciphers/<uuid>")] #[get("/ciphers/<uuid>")]
async fn get_cipher(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_cipher(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await { let cipher = match Cipher::find_by_uuid(uuid, &conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
}; };
if !cipher.is_accessible_to_user(&headers.user.uuid, &mut conn).await { if !cipher.is_accessible_to_user(&headers.user.uuid, &conn).await {
err!("Cipher is not owned by user") err!("Cipher is not owned by user")
} }
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await)) Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await))
} }
#[get("/ciphers/<uuid>/admin")] #[get("/ciphers/<uuid>/admin")]
@ -277,7 +275,7 @@ async fn post_ciphers_admin(
async fn post_ciphers_create( async fn post_ciphers_create(
data: JsonUpcase<ShareCipherData>, data: JsonUpcase<ShareCipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let mut data: ShareCipherData = data.into_inner().data; let mut data: ShareCipherData = data.into_inner().data;
@ -291,11 +289,11 @@ async fn post_ciphers_create(
// This check is usually only needed in update_cipher_from_data(), but we // This check is usually only needed in update_cipher_from_data(), but we
// need it here as well to avoid creating an empty cipher in the call to // need it here as well to avoid creating an empty cipher in the call to
// cipher.save() below. // cipher.save() below.
enforce_personal_ownership_policy(Some(&data.Cipher), &headers, &mut conn).await?; enforce_personal_ownership_policy(Some(&data.Cipher), &headers, &conn).await?;
let mut cipher = Cipher::new(data.Cipher.Type, data.Cipher.Name.clone()); let mut cipher = Cipher::new(data.Cipher.Type, data.Cipher.Name.clone());
cipher.user_uuid = Some(headers.user.uuid.clone()); cipher.user_uuid = Some(headers.user.uuid.clone());
cipher.save(&mut conn).await?; cipher.save(&conn).await?;
// When cloning a cipher, the Bitwarden clients seem to set this field // When cloning a cipher, the Bitwarden clients seem to set this field
// based on the cipher being cloned (when creating a new cipher, it's set // based on the cipher being cloned (when creating a new cipher, it's set
@ -305,12 +303,12 @@ async fn post_ciphers_create(
// or otherwise), we can just ignore this field entirely. // or otherwise), we can just ignore this field entirely.
data.Cipher.LastKnownRevisionDate = None; data.Cipher.LastKnownRevisionDate = None;
share_cipher_by_uuid(&cipher.uuid, data, &headers, &mut conn, &nt).await share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &nt).await
} }
/// Called when creating a new user-owned cipher. /// Called when creating a new user-owned cipher.
#[post("/ciphers", data = "<data>")] #[post("/ciphers", data = "<data>")]
async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
let mut data: CipherData = data.into_inner().data; let mut data: CipherData = data.into_inner().data;
// The web/browser clients set this field to null as expected, but the // The web/browser clients set this field to null as expected, but the
@ -320,9 +318,9 @@ async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, mut conn:
data.LastKnownRevisionDate = None; data.LastKnownRevisionDate = None;
let mut cipher = Cipher::new(data.Type, data.Name.clone()); let mut cipher = Cipher::new(data.Type, data.Name.clone());
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &nt, UpdateType::SyncCipherCreate).await?; update_cipher_from_data(&mut cipher, data, &headers, false, &conn, &nt, UpdateType::SyncCipherCreate).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await)) Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await))
} }
/// Enforces the personal ownership policy on user-owned ciphers, if applicable. /// Enforces the personal ownership policy on user-owned ciphers, if applicable.
@ -332,11 +330,7 @@ async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, mut conn:
/// allowed to delete or share such ciphers to an org, however. /// allowed to delete or share such ciphers to an org, however.
/// ///
/// Ref: https://bitwarden.com/help/article/policies/#personal-ownership /// Ref: https://bitwarden.com/help/article/policies/#personal-ownership
async fn enforce_personal_ownership_policy( async fn enforce_personal_ownership_policy(data: Option<&CipherData>, headers: &Headers, conn: &DbConn) -> EmptyResult {
data: Option<&CipherData>,
headers: &Headers,
conn: &mut DbConn,
) -> EmptyResult {
if data.is_none() || data.unwrap().OrganizationId.is_none() { if data.is_none() || data.unwrap().OrganizationId.is_none() {
let user_uuid = &headers.user.uuid; let user_uuid = &headers.user.uuid;
let policy_type = OrgPolicyType::PersonalOwnership; let policy_type = OrgPolicyType::PersonalOwnership;
@ -352,7 +346,7 @@ pub async fn update_cipher_from_data(
data: CipherData, data: CipherData,
headers: &Headers, headers: &Headers,
shared_to_collection: bool, shared_to_collection: bool,
conn: &mut DbConn, conn: &DbConn,
nt: &Notify<'_>, nt: &Notify<'_>,
ut: UpdateType, ut: UpdateType,
) -> EmptyResult { ) -> EmptyResult {
@ -541,10 +535,10 @@ struct RelationsData {
async fn post_ciphers_import( async fn post_ciphers_import(
data: JsonUpcase<ImportData>, data: JsonUpcase<ImportData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
enforce_personal_ownership_policy(None, &headers, &mut conn).await?; enforce_personal_ownership_policy(None, &headers, &conn).await?;
let data: ImportData = data.into_inner().data; let data: ImportData = data.into_inner().data;
@ -558,7 +552,7 @@ async fn post_ciphers_import(
let mut folders: Vec<_> = Vec::new(); let mut folders: Vec<_> = Vec::new();
for folder in data.Folders.into_iter() { for folder in data.Folders.into_iter() {
let mut new_folder = Folder::new(headers.user.uuid.clone(), folder.Name); let mut new_folder = Folder::new(headers.user.uuid.clone(), folder.Name);
new_folder.save(&mut conn).await?; new_folder.save(&conn).await?;
folders.push(new_folder); folders.push(new_folder);
} }
@ -576,11 +570,11 @@ async fn post_ciphers_import(
cipher_data.FolderId = folder_uuid; cipher_data.FolderId = folder_uuid;
let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone()); let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &mut conn, &nt, UpdateType::None).await?; update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None).await?;
} }
let mut user = headers.user; let mut user = headers.user;
user.update_revision(&mut conn).await?; user.update_revision(&conn).await?;
nt.send_user_update(UpdateType::SyncVault, &user).await; nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(()) Ok(())
@ -625,12 +619,12 @@ async fn put_cipher(
uuid: &str, uuid: &str,
data: JsonUpcase<CipherData>, data: JsonUpcase<CipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let data: CipherData = data.into_inner().data; let data: CipherData = data.into_inner().data;
let mut cipher = match Cipher::find_by_uuid(uuid, &mut conn).await { let mut cipher = match Cipher::find_by_uuid(uuid, &conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
}; };
@ -640,13 +634,13 @@ async fn put_cipher(
// cipher itself, so the user shouldn't need write access to change these. // cipher itself, so the user shouldn't need write access to change these.
// Interestingly, upstream Bitwarden doesn't properly handle this either. // Interestingly, upstream Bitwarden doesn't properly handle this either.
if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await {
err!("Cipher is not write accessible") err!("Cipher is not write accessible")
} }
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &nt, UpdateType::SyncCipherUpdate).await?; update_cipher_from_data(&mut cipher, data, &headers, false, &conn, &nt, UpdateType::SyncCipherUpdate).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await)) Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await))
} }
#[post("/ciphers/<uuid>/partial", data = "<data>")] #[post("/ciphers/<uuid>/partial", data = "<data>")]
@ -665,17 +659,17 @@ async fn put_cipher_partial(
uuid: &str, uuid: &str,
data: JsonUpcase<PartialCipherData>, data: JsonUpcase<PartialCipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
) -> JsonResult { ) -> JsonResult {
let data: PartialCipherData = data.into_inner().data; let data: PartialCipherData = data.into_inner().data;
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await { let cipher = match Cipher::find_by_uuid(uuid, &conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
}; };
if let Some(ref folder_id) = data.FolderId { if let Some(ref folder_id) = data.FolderId {
match Folder::find_by_uuid(folder_id, &mut conn).await { match Folder::find_by_uuid(folder_id, &conn).await {
Some(folder) => { Some(folder) => {
if folder.user_uuid != headers.user.uuid { if folder.user_uuid != headers.user.uuid {
err!("Folder is not owned by user") err!("Folder is not owned by user")
@ -686,11 +680,11 @@ async fn put_cipher_partial(
} }
// Move cipher // Move cipher
cipher.move_to_folder(data.FolderId.clone(), &headers.user.uuid, &mut conn).await?; cipher.move_to_folder(data.FolderId.clone(), &headers.user.uuid, &conn).await?;
// Update favorite // Update favorite
cipher.set_favorite(Some(data.Favorite), &headers.user.uuid, &mut conn).await?; cipher.set_favorite(Some(data.Favorite), &headers.user.uuid, &conn).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await)) Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await))
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -737,35 +731,35 @@ async fn post_collections_admin(
uuid: &str, uuid: &str,
data: JsonUpcase<CollectionsAdminData>, data: JsonUpcase<CollectionsAdminData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let data: CollectionsAdminData = data.into_inner().data; let data: CollectionsAdminData = data.into_inner().data;
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await { let cipher = match Cipher::find_by_uuid(uuid, &conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
}; };
if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await {
err!("Cipher is not write accessible") err!("Cipher is not write accessible")
} }
let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect(); let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect();
let current_collections: HashSet<String> = let current_collections: HashSet<String> =
cipher.get_collections(headers.user.uuid.clone(), &mut conn).await.iter().cloned().collect(); cipher.get_collections(headers.user.uuid.clone(), &conn).await.iter().cloned().collect();
for collection in posted_collections.symmetric_difference(&current_collections) { for collection in posted_collections.symmetric_difference(&current_collections) {
match Collection::find_by_uuid(collection, &mut conn).await { match Collection::find_by_uuid(collection, &conn).await {
None => err!("Invalid collection ID provided"), None => err!("Invalid collection ID provided"),
Some(collection) => { Some(collection) => {
if collection.is_writable_by_user(&headers.user.uuid, &mut conn).await { if collection.is_writable_by_user(&headers.user.uuid, &conn).await {
if posted_collections.contains(&collection.uuid) { if posted_collections.contains(&collection.uuid) {
// Add to collection // Add to collection
CollectionCipher::save(&cipher.uuid, &collection.uuid, &mut conn).await?; CollectionCipher::save(&cipher.uuid, &collection.uuid, &conn).await?;
} else { } else {
// Remove from collection // Remove from collection
CollectionCipher::delete(&cipher.uuid, &collection.uuid, &mut conn).await?; CollectionCipher::delete(&cipher.uuid, &collection.uuid, &conn).await?;
} }
} else { } else {
err!("No rights to modify the collection") err!("No rights to modify the collection")
@ -777,10 +771,10 @@ async fn post_collections_admin(
nt.send_cipher_update( nt.send_cipher_update(
UpdateType::SyncCipherUpdate, UpdateType::SyncCipherUpdate,
&cipher, &cipher,
&cipher.update_users_revision(&mut conn).await, &cipher.update_users_revision(&conn).await,
&headers.device.uuid, &headers.device.uuid,
Some(Vec::from_iter(posted_collections)), Some(Vec::from_iter(posted_collections)),
&mut conn, &conn,
) )
.await; .await;
@ -791,7 +785,7 @@ async fn post_collections_admin(
headers.user.uuid.clone(), headers.user.uuid.clone(),
headers.device.atype, headers.device.atype,
&headers.ip.ip, &headers.ip.ip,
&mut conn, &conn,
) )
.await; .await;
@ -810,12 +804,12 @@ async fn post_cipher_share(
uuid: &str, uuid: &str,
data: JsonUpcase<ShareCipherData>, data: JsonUpcase<ShareCipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let data: ShareCipherData = data.into_inner().data; let data: ShareCipherData = data.into_inner().data;
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await share_cipher_by_uuid(uuid, data, &headers, &conn, &nt).await
} }
#[put("/ciphers/<uuid>/share", data = "<data>")] #[put("/ciphers/<uuid>/share", data = "<data>")]
@ -823,12 +817,12 @@ async fn put_cipher_share(
uuid: &str, uuid: &str,
data: JsonUpcase<ShareCipherData>, data: JsonUpcase<ShareCipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let data: ShareCipherData = data.into_inner().data; let data: ShareCipherData = data.into_inner().data;
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await share_cipher_by_uuid(uuid, data, &headers, &conn, &nt).await
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -842,7 +836,7 @@ struct ShareSelectedCipherData {
async fn put_cipher_share_selected( async fn put_cipher_share_selected(
data: JsonUpcase<ShareSelectedCipherData>, data: JsonUpcase<ShareSelectedCipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let mut data: ShareSelectedCipherData = data.into_inner().data; let mut data: ShareSelectedCipherData = data.into_inner().data;
@ -870,7 +864,7 @@ async fn put_cipher_share_selected(
}; };
match shared_cipher_data.Cipher.Id.take() { match shared_cipher_data.Cipher.Id.take() {
Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &mut conn, &nt).await?, Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt).await?,
None => err!("Request missing ids field"), None => err!("Request missing ids field"),
}; };
} }
@ -882,7 +876,7 @@ async fn share_cipher_by_uuid(
uuid: &str, uuid: &str,
data: ShareCipherData, data: ShareCipherData,
headers: &Headers, headers: &Headers,
conn: &mut DbConn, conn: &DbConn,
nt: &Notify<'_>, nt: &Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let mut cipher = match Cipher::find_by_uuid(uuid, conn).await { let mut cipher = match Cipher::find_by_uuid(uuid, conn).await {
@ -933,17 +927,17 @@ async fn share_cipher_by_uuid(
/// their object storage service. For self-hosted instances, it basically just /// their object storage service. For self-hosted instances, it basically just
/// redirects to the same location as before the v2 API. /// redirects to the same location as before the v2 API.
#[get("/ciphers/<uuid>/attachment/<attachment_id>")] #[get("/ciphers/<uuid>/attachment/<attachment_id>")]
async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, conn: DbConn) -> JsonResult {
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await { let cipher = match Cipher::find_by_uuid(uuid, &conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
}; };
if !cipher.is_accessible_to_user(&headers.user.uuid, &mut conn).await { if !cipher.is_accessible_to_user(&headers.user.uuid, &conn).await {
err!("Cipher is not accessible") err!("Cipher is not accessible")
} }
match Attachment::find_by_id(attachment_id, &mut conn).await { match Attachment::find_by_id(attachment_id, &conn).await {
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))), Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))),
Some(_) => err!("Attachment doesn't belong to cipher"), Some(_) => err!("Attachment doesn't belong to cipher"),
None => err!("Attachment doesn't exist"), None => err!("Attachment doesn't exist"),
@ -973,14 +967,14 @@ async fn post_attachment_v2(
uuid: &str, uuid: &str,
data: JsonUpcase<AttachmentRequestData>, data: JsonUpcase<AttachmentRequestData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
) -> JsonResult { ) -> JsonResult {
let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await { let cipher = match Cipher::find_by_uuid(uuid, &conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
}; };
if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await {
err!("Cipher is not write accessible") err!("Cipher is not write accessible")
} }
@ -988,7 +982,7 @@ async fn post_attachment_v2(
let data: AttachmentRequestData = data.into_inner().data; let data: AttachmentRequestData = data.into_inner().data;
let attachment = let attachment =
Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.FileName, data.FileSize, Some(data.Key)); Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.FileName, data.FileSize, Some(data.Key));
attachment.save(&mut conn).await.expect("Error saving attachment"); attachment.save(&conn).await.expect("Error saving attachment");
let url = format!("/ciphers/{}/attachment/{}", cipher.uuid, attachment_id); let url = format!("/ciphers/{}/attachment/{}", cipher.uuid, attachment_id);
let response_key = match data.AdminRequest { let response_key = match data.AdminRequest {
@ -1001,7 +995,7 @@ async fn post_attachment_v2(
"AttachmentId": attachment_id, "AttachmentId": attachment_id,
"Url": url, "Url": url,
"FileUploadType": FileUploadType::Direct as i32, "FileUploadType": FileUploadType::Direct as i32,
response_key: cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await, response_key: cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await,
}))) })))
} }
@ -1024,15 +1018,15 @@ async fn save_attachment(
cipher_uuid: &str, cipher_uuid: &str,
data: Form<UploadData<'_>>, data: Form<UploadData<'_>>,
headers: &Headers, headers: &Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> Result<(Cipher, DbConn), crate::error::Error> { ) -> Result<(Cipher, DbConn), crate::error::Error> {
let cipher = match Cipher::find_by_uuid(cipher_uuid, &mut conn).await { let cipher = match Cipher::find_by_uuid(cipher_uuid, &conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
}; };
if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await {
err!("Cipher is not write accessible") err!("Cipher is not write accessible")
} }
@ -1047,7 +1041,7 @@ async fn save_attachment(
match CONFIG.user_attachment_limit() { match CONFIG.user_attachment_limit() {
Some(0) => err!("Attachments are disabled"), Some(0) => err!("Attachments are disabled"),
Some(limit_kb) => { Some(limit_kb) => {
let left = (limit_kb * 1024) - Attachment::size_by_user(user_uuid, &mut conn).await + size_adjust; let left = (limit_kb * 1024) - Attachment::size_by_user(user_uuid, &conn).await + size_adjust;
if left <= 0 { if left <= 0 {
err!("Attachment storage limit reached! Delete some attachments to free up space") err!("Attachment storage limit reached! Delete some attachments to free up space")
} }
@ -1059,7 +1053,7 @@ async fn save_attachment(
match CONFIG.org_attachment_limit() { match CONFIG.org_attachment_limit() {
Some(0) => err!("Attachments are disabled"), Some(0) => err!("Attachments are disabled"),
Some(limit_kb) => { Some(limit_kb) => {
let left = (limit_kb * 1024) - Attachment::size_by_org(org_uuid, &mut conn).await + size_adjust; let left = (limit_kb * 1024) - Attachment::size_by_org(org_uuid, &conn).await + size_adjust;
if left <= 0 { if left <= 0 {
err!("Attachment storage limit reached! Delete some attachments to free up space") err!("Attachment storage limit reached! Delete some attachments to free up space")
} }
@ -1103,10 +1097,10 @@ async fn save_attachment(
if size != attachment.file_size { if size != attachment.file_size {
// Update the attachment with the actual file size. // Update the attachment with the actual file size.
attachment.file_size = size; attachment.file_size = size;
attachment.save(&mut conn).await.expect("Error updating attachment"); attachment.save(&conn).await.expect("Error updating attachment");
} }
} else { } else {
attachment.delete(&mut conn).await.ok(); attachment.delete(&conn).await.ok();
err!(format!("Attachment size mismatch (expected within [{min_size}, {max_size}], got {size})")); err!(format!("Attachment size mismatch (expected within [{min_size}, {max_size}], got {size})"));
} }
@ -1122,7 +1116,7 @@ async fn save_attachment(
} }
let attachment = let attachment =
Attachment::new(file_id, String::from(cipher_uuid), encrypted_filename.unwrap(), size, data.key); Attachment::new(file_id, String::from(cipher_uuid), encrypted_filename.unwrap(), size, data.key);
attachment.save(&mut conn).await.expect("Error saving attachment"); attachment.save(&conn).await.expect("Error saving attachment");
} }
if let Err(_err) = data.data.persist_to(&file_path).await { if let Err(_err) = data.data.persist_to(&file_path).await {
@ -1132,10 +1126,10 @@ async fn save_attachment(
nt.send_cipher_update( nt.send_cipher_update(
UpdateType::SyncCipherUpdate, UpdateType::SyncCipherUpdate,
&cipher, &cipher,
&cipher.update_users_revision(&mut conn).await, &cipher.update_users_revision(&conn).await,
&headers.device.uuid, &headers.device.uuid,
None, None,
&mut conn, &conn,
) )
.await; .await;
@ -1147,7 +1141,7 @@ async fn save_attachment(
headers.user.uuid.clone(), headers.user.uuid.clone(),
headers.device.atype, headers.device.atype,
&headers.ip.ip, &headers.ip.ip,
&mut conn, &conn,
) )
.await; .await;
} }
@ -1165,10 +1159,10 @@ async fn post_attachment_v2_data(
attachment_id: &str, attachment_id: &str,
data: Form<UploadData<'_>>, data: Form<UploadData<'_>>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let attachment = match Attachment::find_by_id(attachment_id, &mut conn).await { let attachment = match Attachment::find_by_id(attachment_id, &conn).await {
Some(attachment) if uuid == attachment.cipher_uuid => Some(attachment), Some(attachment) if uuid == attachment.cipher_uuid => Some(attachment),
Some(_) => err!("Attachment doesn't belong to cipher"), Some(_) => err!("Attachment doesn't belong to cipher"),
None => err!("Attachment doesn't exist"), None => err!("Attachment doesn't exist"),
@ -1192,9 +1186,9 @@ async fn post_attachment(
// the attachment database record as well as saving the data to disk. // the attachment database record as well as saving the data to disk.
let attachment = None; let attachment = None;
let (cipher, mut conn) = save_attachment(attachment, uuid, data, &headers, conn, nt).await?; let (cipher, conn) = save_attachment(attachment, uuid, data, &headers, conn, nt).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await)) Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await))
} }
#[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")] #[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")]
@ -1214,10 +1208,10 @@ async fn post_attachment_share(
attachment_id: &str, attachment_id: &str,
data: Form<UploadData<'_>>, data: Form<UploadData<'_>>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await?; _delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &conn, &nt).await?;
post_attachment(uuid, data, headers, conn, nt).await post_attachment(uuid, data, headers, conn, nt).await
} }
@ -1248,10 +1242,10 @@ async fn delete_attachment(
uuid: &str, uuid: &str,
attachment_id: &str, attachment_id: &str,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await _delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &conn, &nt).await
} }
#[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")] #[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")]
@ -1259,44 +1253,44 @@ async fn delete_attachment_admin(
uuid: &str, uuid: &str,
attachment_id: &str, attachment_id: &str,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
_delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &mut conn, &nt).await _delete_cipher_attachment_by_id(uuid, attachment_id, &headers, &conn, &nt).await
} }
#[post("/ciphers/<uuid>/delete")] #[post("/ciphers/<uuid>/delete")]
async fn delete_cipher_post(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_post(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await _delete_cipher_by_uuid(uuid, &headers, &conn, false, &nt).await
// permanent delete // permanent delete
} }
#[post("/ciphers/<uuid>/delete-admin")] #[post("/ciphers/<uuid>/delete-admin")]
async fn delete_cipher_post_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_post_admin(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await _delete_cipher_by_uuid(uuid, &headers, &conn, false, &nt).await
// permanent delete // permanent delete
} }
#[put("/ciphers/<uuid>/delete")] #[put("/ciphers/<uuid>/delete")]
async fn delete_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_put(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await _delete_cipher_by_uuid(uuid, &headers, &conn, true, &nt).await
// soft delete // soft delete
} }
#[put("/ciphers/<uuid>/delete-admin")] #[put("/ciphers/<uuid>/delete-admin")]
async fn delete_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_put_admin(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await _delete_cipher_by_uuid(uuid, &headers, &conn, true, &nt).await
} }
#[delete("/ciphers/<uuid>")] #[delete("/ciphers/<uuid>")]
async fn delete_cipher(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await _delete_cipher_by_uuid(uuid, &headers, &conn, false, &nt).await
// permanent delete // permanent delete
} }
#[delete("/ciphers/<uuid>/admin")] #[delete("/ciphers/<uuid>/admin")]
async fn delete_cipher_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_admin(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await _delete_cipher_by_uuid(uuid, &headers, &conn, false, &nt).await
// permanent delete // permanent delete
} }
@ -1361,23 +1355,23 @@ async fn delete_cipher_selected_put_admin(
} }
#[put("/ciphers/<uuid>/restore")] #[put("/ciphers/<uuid>/restore")]
async fn restore_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn restore_cipher_put(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await _restore_cipher_by_uuid(uuid, &headers, &conn, &nt).await
} }
#[put("/ciphers/<uuid>/restore-admin")] #[put("/ciphers/<uuid>/restore-admin")]
async fn restore_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn restore_cipher_put_admin(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await _restore_cipher_by_uuid(uuid, &headers, &conn, &nt).await
} }
#[put("/ciphers/restore", data = "<data>")] #[put("/ciphers/restore", data = "<data>")]
async fn restore_cipher_selected( async fn restore_cipher_selected(
data: JsonUpcase<Value>, data: JsonUpcase<Value>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
_restore_multiple_ciphers(data, &headers, &mut conn, &nt).await _restore_multiple_ciphers(data, &headers, &conn, &nt).await
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -1391,14 +1385,14 @@ struct MoveCipherData {
async fn move_cipher_selected( async fn move_cipher_selected(
data: JsonUpcase<MoveCipherData>, data: JsonUpcase<MoveCipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let data = data.into_inner().data; let data = data.into_inner().data;
let user_uuid = headers.user.uuid; let user_uuid = headers.user.uuid;
if let Some(ref folder_id) = data.FolderId { if let Some(ref folder_id) = data.FolderId {
match Folder::find_by_uuid(folder_id, &mut conn).await { match Folder::find_by_uuid(folder_id, &conn).await {
Some(folder) => { Some(folder) => {
if folder.user_uuid != user_uuid { if folder.user_uuid != user_uuid {
err!("Folder is not owned by user") err!("Folder is not owned by user")
@ -1409,17 +1403,17 @@ async fn move_cipher_selected(
} }
for uuid in data.Ids { for uuid in data.Ids {
let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await { let cipher = match Cipher::find_by_uuid(&uuid, &conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
}; };
if !cipher.is_accessible_to_user(&user_uuid, &mut conn).await { if !cipher.is_accessible_to_user(&user_uuid, &conn).await {
err!("Cipher is not accessible by user") err!("Cipher is not accessible by user")
} }
// Move cipher // Move cipher
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?; cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &conn).await?;
nt.send_cipher_update( nt.send_cipher_update(
UpdateType::SyncCipherUpdate, UpdateType::SyncCipherUpdate,
@ -1427,7 +1421,7 @@ async fn move_cipher_selected(
&[user_uuid.clone()], &[user_uuid.clone()],
&headers.device.uuid, &headers.device.uuid,
None, None,
&mut conn, &conn,
) )
.await; .await;
} }
@ -1456,7 +1450,7 @@ async fn delete_all(
organization: Option<OrganizationId>, organization: Option<OrganizationId>,
data: JsonUpcase<PasswordData>, data: JsonUpcase<PasswordData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let data: PasswordData = data.into_inner().data; let data: PasswordData = data.into_inner().data;
@ -1471,11 +1465,11 @@ async fn delete_all(
match organization { match organization {
Some(org_data) => { Some(org_data) => {
// Organization ID in query params, purging organization vault // Organization ID in query params, purging organization vault
match UserOrganization::find_by_user_and_org(&user.uuid, &org_data.org_id, &mut conn).await { match UserOrganization::find_by_user_and_org(&user.uuid, &org_data.org_id, &conn).await {
None => err!("You don't have permission to purge the organization vault"), None => err!("You don't have permission to purge the organization vault"),
Some(user_org) => { Some(user_org) => {
if user_org.atype == UserOrgType::Owner { if user_org.atype == UserOrgType::Owner {
Cipher::delete_all_by_organization(&org_data.org_id, &mut conn).await?; Cipher::delete_all_by_organization(&org_data.org_id, &conn).await?;
nt.send_user_update(UpdateType::SyncVault, &user).await; nt.send_user_update(UpdateType::SyncVault, &user).await;
log_event( log_event(
@ -1485,7 +1479,7 @@ async fn delete_all(
user.uuid, user.uuid,
headers.device.atype, headers.device.atype,
&headers.ip.ip, &headers.ip.ip,
&mut conn, &conn,
) )
.await; .await;
@ -1499,16 +1493,16 @@ async fn delete_all(
None => { None => {
// No organization ID in query params, purging user vault // No organization ID in query params, purging user vault
// Delete ciphers and their attachments // Delete ciphers and their attachments
for cipher in Cipher::find_owned_by_user(&user.uuid, &mut conn).await { for cipher in Cipher::find_owned_by_user(&user.uuid, &conn).await {
cipher.delete(&mut conn).await?; cipher.delete(&conn).await?;
} }
// Delete folders // Delete folders
for f in Folder::find_by_user(&user.uuid, &mut conn).await { for f in Folder::find_by_user(&user.uuid, &conn).await {
f.delete(&mut conn).await?; f.delete(&conn).await?;
} }
user.update_revision(&mut conn).await?; user.update_revision(&conn).await?;
nt.send_user_update(UpdateType::SyncVault, &user).await; nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(()) Ok(())
@ -1519,7 +1513,7 @@ async fn delete_all(
async fn _delete_cipher_by_uuid( async fn _delete_cipher_by_uuid(
uuid: &str, uuid: &str,
headers: &Headers, headers: &Headers,
conn: &mut DbConn, conn: &DbConn,
soft_delete: bool, soft_delete: bool,
nt: &Notify<'_>, nt: &Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
@ -1581,7 +1575,7 @@ async fn _delete_cipher_by_uuid(
async fn _delete_multiple_ciphers( async fn _delete_multiple_ciphers(
data: JsonUpcase<Value>, data: JsonUpcase<Value>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
soft_delete: bool, soft_delete: bool,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
@ -1596,7 +1590,7 @@ async fn _delete_multiple_ciphers(
}; };
for uuid in uuids { for uuid in uuids {
if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &mut conn, soft_delete, &nt).await { if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &conn, soft_delete, &nt).await {
return error; return error;
}; };
} }
@ -1604,7 +1598,7 @@ async fn _delete_multiple_ciphers(
Ok(()) Ok(())
} }
async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbConn, nt: &Notify<'_>) -> JsonResult { async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn, nt: &Notify<'_>) -> JsonResult {
let mut cipher = match Cipher::find_by_uuid(uuid, conn).await { let mut cipher = match Cipher::find_by_uuid(uuid, conn).await {
Some(cipher) => cipher, Some(cipher) => cipher,
None => err!("Cipher doesn't exist"), None => err!("Cipher doesn't exist"),
@ -1646,7 +1640,7 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbCon
async fn _restore_multiple_ciphers( async fn _restore_multiple_ciphers(
data: JsonUpcase<Value>, data: JsonUpcase<Value>,
headers: &Headers, headers: &Headers,
conn: &mut DbConn, conn: &DbConn,
nt: &Notify<'_>, nt: &Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let data: Value = data.into_inner().data; let data: Value = data.into_inner().data;
@ -1678,7 +1672,7 @@ async fn _delete_cipher_attachment_by_id(
uuid: &str, uuid: &str,
attachment_id: &str, attachment_id: &str,
headers: &Headers, headers: &Headers,
conn: &mut DbConn, conn: &DbConn,
nt: &Notify<'_>, nt: &Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let attachment = match Attachment::find_by_id(attachment_id, conn).await { let attachment = match Attachment::find_by_id(attachment_id, conn).await {
@ -1748,7 +1742,7 @@ pub enum CipherSyncType {
} }
impl CipherSyncData { impl CipherSyncData {
pub async fn new(user_uuid: &str, sync_type: CipherSyncType, conn: &mut DbConn) -> Self { pub async fn new(user_uuid: &str, sync_type: CipherSyncType, conn: &DbConn) -> Self {
let cipher_folders: HashMap<String, String>; let cipher_folders: HashMap<String, String>;
let cipher_favorites: HashSet<String>; let cipher_favorites: HashSet<String>;
match sync_type { match sync_type {

Datei anzeigen

@ -37,13 +37,13 @@ pub fn routes() -> Vec<Route> {
// region get // region get
#[get("/emergency-access/trusted")] #[get("/emergency-access/trusted")]
async fn get_contacts(headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let emergency_access_list = EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &mut conn).await; let emergency_access_list = EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &conn).await;
let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len()); let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len());
for ea in emergency_access_list { for ea in emergency_access_list {
emergency_access_list_json.push(ea.to_json_grantee_details(&mut conn).await); emergency_access_list_json.push(ea.to_json_grantee_details(&conn).await);
} }
Ok(Json(json!({ Ok(Json(json!({
@ -54,13 +54,13 @@ async fn get_contacts(headers: Headers, mut conn: DbConn) -> JsonResult {
} }
#[get("/emergency-access/granted")] #[get("/emergency-access/granted")]
async fn get_grantees(headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let emergency_access_list = EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &mut conn).await; let emergency_access_list = EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &conn).await;
let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len()); let mut emergency_access_list_json = Vec::with_capacity(emergency_access_list.len());
for ea in emergency_access_list { for ea in emergency_access_list {
emergency_access_list_json.push(ea.to_json_grantor_details(&mut conn).await); emergency_access_list_json.push(ea.to_json_grantor_details(&conn).await);
} }
Ok(Json(json!({ Ok(Json(json!({
@ -71,11 +71,11 @@ async fn get_grantees(headers: Headers, mut conn: DbConn) -> JsonResult {
} }
#[get("/emergency-access/<emer_id>")] #[get("/emergency-access/<emer_id>")]
async fn get_emergency_access(emer_id: &str, mut conn: DbConn) -> JsonResult { async fn get_emergency_access(emer_id: &str, conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&mut conn).await)), Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&conn).await)),
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
} }
} }
@ -98,16 +98,12 @@ async fn put_emergency_access(emer_id: &str, data: JsonUpcase<EmergencyAccessUpd
} }
#[post("/emergency-access/<emer_id>", data = "<data>")] #[post("/emergency-access/<emer_id>", data = "<data>")]
async fn post_emergency_access( async fn post_emergency_access(emer_id: &str, data: JsonUpcase<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult {
emer_id: &str,
data: JsonUpcase<EmergencyAccessUpdateData>,
mut conn: DbConn,
) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let data: EmergencyAccessUpdateData = data.into_inner().data; let data: EmergencyAccessUpdateData = data.into_inner().data;
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emergency_access) => emergency_access, Some(emergency_access) => emergency_access,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -123,7 +119,7 @@ async fn post_emergency_access(
emergency_access.key_encrypted = data.KeyEncrypted; emergency_access.key_encrypted = data.KeyEncrypted;
} }
emergency_access.save(&mut conn).await?; emergency_access.save(&conn).await?;
Ok(Json(emergency_access.to_json())) Ok(Json(emergency_access.to_json()))
} }
@ -132,12 +128,12 @@ async fn post_emergency_access(
// region delete // region delete
#[delete("/emergency-access/<emer_id>")] #[delete("/emergency-access/<emer_id>")]
async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn delete_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> EmptyResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let grantor_user = headers.user; let grantor_user = headers.user;
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => { Some(emer) => {
if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) { if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) {
err!("Emergency access not valid.") err!("Emergency access not valid.")
@ -146,7 +142,7 @@ async fn delete_emergency_access(emer_id: &str, headers: Headers, mut conn: DbCo
} }
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
emergency_access.delete(&mut conn).await?; emergency_access.delete(&conn).await?;
Ok(()) Ok(())
} }
@ -168,7 +164,7 @@ struct EmergencyAccessInviteData {
} }
#[post("/emergency-access/invite", data = "<data>")] #[post("/emergency-access/invite", data = "<data>")]
async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, conn: DbConn) -> EmptyResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let data: EmergencyAccessInviteData = data.into_inner().data; let data: EmergencyAccessInviteData = data.into_inner().data;
@ -189,7 +185,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
err!("You can not set yourself as an emergency contact.") err!("You can not set yourself as an emergency contact.")
} }
let grantee_user = match User::find_by_mail(&email, &mut conn).await { let grantee_user = match User::find_by_mail(&email, &conn).await {
None => { None => {
if !CONFIG.invitations_allowed() { if !CONFIG.invitations_allowed() {
err!(format!("Grantee user does not exist: {}", &email)) err!(format!("Grantee user does not exist: {}", &email))
@ -201,11 +197,11 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
if !CONFIG.mail_enabled() { if !CONFIG.mail_enabled() {
let invitation = Invitation::new(&email); let invitation = Invitation::new(&email);
invitation.save(&mut conn).await?; invitation.save(&conn).await?;
} }
let mut user = User::new(email.clone()); let mut user = User::new(email.clone());
user.save(&mut conn).await?; user.save(&conn).await?;
user user
} }
Some(user) => user, Some(user) => user,
@ -215,7 +211,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
&grantor_user.uuid, &grantor_user.uuid,
&grantee_user.uuid, &grantee_user.uuid,
&grantee_user.email, &grantee_user.email,
&mut conn, &conn,
) )
.await .await
.is_some() .is_some()
@ -225,7 +221,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
let mut new_emergency_access = let mut new_emergency_access =
EmergencyAccess::new(grantor_user.uuid, grantee_user.email, emergency_access_status, new_type, wait_time_days); EmergencyAccess::new(grantor_user.uuid, grantee_user.email, emergency_access_status, new_type, wait_time_days);
new_emergency_access.save(&mut conn).await?; new_emergency_access.save(&conn).await?;
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
mail::send_emergency_access_invite( mail::send_emergency_access_invite(
@ -238,8 +234,8 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
.await?; .await?;
} else { } else {
// Automatically mark user as accepted if no email invites // Automatically mark user as accepted if no email invites
match User::find_by_mail(&email, &mut conn).await { match User::find_by_mail(&email, &conn).await {
Some(user) => match accept_invite_process(&user.uuid, &mut new_emergency_access, &email, &mut conn).await { Some(user) => match accept_invite_process(&user.uuid, &mut new_emergency_access, &email, &conn).await {
Ok(v) => v, Ok(v) => v,
Err(e) => err!(e.to_string()), Err(e) => err!(e.to_string()),
}, },
@ -251,10 +247,10 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
} }
#[post("/emergency-access/<emer_id>/reinvite")] #[post("/emergency-access/<emer_id>/reinvite")]
async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn resend_invite(emer_id: &str, headers: Headers, conn: DbConn) -> EmptyResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -272,7 +268,7 @@ async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> Emp
None => err!("Email not valid."), None => err!("Email not valid."),
}; };
let grantee_user = match User::find_by_mail(&email, &mut conn).await { let grantee_user = match User::find_by_mail(&email, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantee user not found."), None => err!("Grantee user not found."),
}; };
@ -289,13 +285,13 @@ async fn resend_invite(emer_id: &str, headers: Headers, mut conn: DbConn) -> Emp
) )
.await?; .await?;
} else { } else {
if Invitation::find_by_mail(&email, &mut conn).await.is_none() { if Invitation::find_by_mail(&email, &conn).await.is_none() {
let invitation = Invitation::new(&email); let invitation = Invitation::new(&email);
invitation.save(&mut conn).await?; invitation.save(&conn).await?;
} }
// Automatically mark user as accepted if no email invites // Automatically mark user as accepted if no email invites
match accept_invite_process(&grantee_user.uuid, &mut emergency_access, &email, &mut conn).await { match accept_invite_process(&grantee_user.uuid, &mut emergency_access, &email, &conn).await {
Ok(v) => v, Ok(v) => v,
Err(e) => err!(e.to_string()), Err(e) => err!(e.to_string()),
} }
@ -311,7 +307,7 @@ struct AcceptData {
} }
#[post("/emergency-access/<emer_id>/accept", data = "<data>")] #[post("/emergency-access/<emer_id>/accept", data = "<data>")]
async fn accept_invite(emer_id: &str, data: JsonUpcase<AcceptData>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn accept_invite(emer_id: &str, data: JsonUpcase<AcceptData>, headers: Headers, conn: DbConn) -> EmptyResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let data: AcceptData = data.into_inner().data; let data: AcceptData = data.into_inner().data;
@ -324,21 +320,21 @@ async fn accept_invite(emer_id: &str, data: JsonUpcase<AcceptData>, headers: Hea
err!("Claim email does not match current users email") err!("Claim email does not match current users email")
} }
let grantee_user = match User::find_by_mail(&claims.email, &mut conn).await { let grantee_user = match User::find_by_mail(&claims.email, &conn).await {
Some(user) => { Some(user) => {
Invitation::take(&claims.email, &mut conn).await; Invitation::take(&claims.email, &conn).await;
user user
} }
None => err!("Invited user not found"), None => err!("Invited user not found"),
}; };
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
// get grantor user to send Accepted email // get grantor user to send Accepted email
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantor user not found."), None => err!("Grantor user not found."),
}; };
@ -347,7 +343,7 @@ async fn accept_invite(emer_id: &str, data: JsonUpcase<AcceptData>, headers: Hea
&& grantor_user.name == claims.grantor_name && grantor_user.name == claims.grantor_name
&& grantor_user.email == claims.grantor_email && grantor_user.email == claims.grantor_email
{ {
match accept_invite_process(&grantee_user.uuid, &mut emergency_access, &grantee_user.email, &mut conn).await { match accept_invite_process(&grantee_user.uuid, &mut emergency_access, &grantee_user.email, &conn).await {
Ok(v) => v, Ok(v) => v,
Err(e) => err!(e.to_string()), Err(e) => err!(e.to_string()),
} }
@ -366,7 +362,7 @@ async fn accept_invite_process(
grantee_uuid: &str, grantee_uuid: &str,
emergency_access: &mut EmergencyAccess, emergency_access: &mut EmergencyAccess,
grantee_email: &str, grantee_email: &str,
conn: &mut DbConn, conn: &DbConn,
) -> EmptyResult { ) -> EmptyResult {
if emergency_access.email.is_none() || emergency_access.email.as_ref().unwrap() != grantee_email { if emergency_access.email.is_none() || emergency_access.email.as_ref().unwrap() != grantee_email {
err!("User email does not match invite."); err!("User email does not match invite.");
@ -393,7 +389,7 @@ async fn confirm_emergency_access(
emer_id: &str, emer_id: &str,
data: JsonUpcase<ConfirmData>, data: JsonUpcase<ConfirmData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
) -> JsonResult { ) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
@ -401,7 +397,7 @@ async fn confirm_emergency_access(
let data: ConfirmData = data.into_inner().data; let data: ConfirmData = data.into_inner().data;
let key = data.Key; let key = data.Key;
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -412,13 +408,13 @@ async fn confirm_emergency_access(
err!("Emergency access not valid.") err!("Emergency access not valid.")
} }
let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &mut conn).await { let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantor user not found."), None => err!("Grantor user not found."),
}; };
if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() {
let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await { let grantee_user = match User::find_by_uuid(grantee_uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantee user not found."), None => err!("Grantee user not found."),
}; };
@ -427,7 +423,7 @@ async fn confirm_emergency_access(
emergency_access.key_encrypted = Some(key); emergency_access.key_encrypted = Some(key);
emergency_access.email = None; emergency_access.email = None;
emergency_access.save(&mut conn).await?; emergency_access.save(&conn).await?;
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
mail::send_emergency_access_invite_confirmed(&grantee_user.email, &grantor_user.name).await?; mail::send_emergency_access_invite_confirmed(&grantee_user.email, &grantor_user.name).await?;
@ -443,11 +439,11 @@ async fn confirm_emergency_access(
// region access emergency access // region access emergency access
#[post("/emergency-access/<emer_id>/initiate")] #[post("/emergency-access/<emer_id>/initiate")]
async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn initiate_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let initiating_user = headers.user; let initiating_user = headers.user;
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -458,7 +454,7 @@ async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
err!("Emergency access not valid.") err!("Emergency access not valid.")
} }
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantor user not found."), None => err!("Grantor user not found."),
}; };
@ -468,7 +464,7 @@ async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
emergency_access.updated_at = now; emergency_access.updated_at = now;
emergency_access.recovery_initiated_at = Some(now); emergency_access.recovery_initiated_at = Some(now);
emergency_access.last_notification_at = Some(now); emergency_access.last_notification_at = Some(now);
emergency_access.save(&mut conn).await?; emergency_access.save(&conn).await?;
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
mail::send_emergency_access_recovery_initiated( mail::send_emergency_access_recovery_initiated(
@ -483,10 +479,10 @@ async fn initiate_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
} }
#[post("/emergency-access/<emer_id>/approve")] #[post("/emergency-access/<emer_id>/approve")]
async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn approve_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -497,19 +493,19 @@ async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbC
err!("Emergency access not valid.") err!("Emergency access not valid.")
} }
let grantor_user = match User::find_by_uuid(&headers.user.uuid, &mut conn).await { let grantor_user = match User::find_by_uuid(&headers.user.uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantor user not found."), None => err!("Grantor user not found."),
}; };
if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() {
let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await { let grantee_user = match User::find_by_uuid(grantee_uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantee user not found."), None => err!("Grantee user not found."),
}; };
emergency_access.status = EmergencyAccessStatus::RecoveryApproved as i32; emergency_access.status = EmergencyAccessStatus::RecoveryApproved as i32;
emergency_access.save(&mut conn).await?; emergency_access.save(&conn).await?;
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name).await?; mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name).await?;
@ -521,10 +517,10 @@ async fn approve_emergency_access(emer_id: &str, headers: Headers, mut conn: DbC
} }
#[post("/emergency-access/<emer_id>/reject")] #[post("/emergency-access/<emer_id>/reject")]
async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn reject_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let mut emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -536,19 +532,19 @@ async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbCo
err!("Emergency access not valid.") err!("Emergency access not valid.")
} }
let grantor_user = match User::find_by_uuid(&headers.user.uuid, &mut conn).await { let grantor_user = match User::find_by_uuid(&headers.user.uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantor user not found."), None => err!("Grantor user not found."),
}; };
if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() {
let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await { let grantee_user = match User::find_by_uuid(grantee_uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantee user not found."), None => err!("Grantee user not found."),
}; };
emergency_access.status = EmergencyAccessStatus::Confirmed as i32; emergency_access.status = EmergencyAccessStatus::Confirmed as i32;
emergency_access.save(&mut conn).await?; emergency_access.save(&conn).await?;
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name).await?; mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name).await?;
@ -564,10 +560,10 @@ async fn reject_emergency_access(emer_id: &str, headers: Headers, mut conn: DbCo
// region action // region action
#[post("/emergency-access/<emer_id>/view")] #[post("/emergency-access/<emer_id>/view")]
async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn view_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -576,8 +572,8 @@ async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn
err!("Emergency access not valid.") err!("Emergency access not valid.")
} }
let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &mut conn).await; let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &conn).await;
let cipher_sync_data = CipherSyncData::new(&emergency_access.grantor_uuid, CipherSyncType::User, &mut conn).await; let cipher_sync_data = CipherSyncData::new(&emergency_access.grantor_uuid, CipherSyncType::User, &conn).await;
let mut ciphers_json = Vec::with_capacity(ciphers.len()); let mut ciphers_json = Vec::with_capacity(ciphers.len());
for c in ciphers { for c in ciphers {
@ -587,7 +583,7 @@ async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn
&emergency_access.grantor_uuid, &emergency_access.grantor_uuid,
Some(&cipher_sync_data), Some(&cipher_sync_data),
CipherSyncType::User, CipherSyncType::User,
&mut conn, &conn,
) )
.await, .await,
); );
@ -601,11 +597,11 @@ async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn
} }
#[post("/emergency-access/<emer_id>/takeover")] #[post("/emergency-access/<emer_id>/takeover")]
async fn takeover_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn takeover_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> JsonResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
let requesting_user = headers.user; let requesting_user = headers.user;
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -614,7 +610,7 @@ async fn takeover_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
err!("Emergency access not valid.") err!("Emergency access not valid.")
} }
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantor user not found."), None => err!("Grantor user not found."),
}; };
@ -643,7 +639,7 @@ async fn password_emergency_access(
emer_id: &str, emer_id: &str,
data: JsonUpcase<EmergencyAccessPasswordData>, data: JsonUpcase<EmergencyAccessPasswordData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
) -> EmptyResult { ) -> EmptyResult {
check_emergency_access_allowed()?; check_emergency_access_allowed()?;
@ -652,7 +648,7 @@ async fn password_emergency_access(
//let key = &data.Key; //let key = &data.Key;
let requesting_user = headers.user; let requesting_user = headers.user;
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -661,22 +657,22 @@ async fn password_emergency_access(
err!("Emergency access not valid.") err!("Emergency access not valid.")
} }
let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantor user not found."), None => err!("Grantor user not found."),
}; };
// change grantor_user password // change grantor_user password
grantor_user.set_password(new_master_password_hash, Some(data.Key), true, None); grantor_user.set_password(new_master_password_hash, Some(data.Key), true, None);
grantor_user.save(&mut conn).await?; grantor_user.save(&conn).await?;
// Disable TwoFactor providers since they will otherwise block logins // Disable TwoFactor providers since they will otherwise block logins
TwoFactor::delete_all_by_user(&grantor_user.uuid, &mut conn).await?; TwoFactor::delete_all_by_user(&grantor_user.uuid, &conn).await?;
// Remove grantor from all organisations unless Owner // Remove grantor from all organisations unless Owner
for user_org in UserOrganization::find_any_state_by_user(&grantor_user.uuid, &mut conn).await { for user_org in UserOrganization::find_any_state_by_user(&grantor_user.uuid, &conn).await {
if user_org.atype != UserOrgType::Owner as i32 { if user_org.atype != UserOrgType::Owner as i32 {
user_org.delete(&mut conn).await?; user_org.delete(&conn).await?;
} }
} }
Ok(()) Ok(())
@ -685,9 +681,9 @@ async fn password_emergency_access(
// endregion // endregion
#[get("/emergency-access/<emer_id>/policies")] #[get("/emergency-access/<emer_id>/policies")]
async fn policies_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn policies_emergency_access(emer_id: &str, headers: Headers, conn: DbConn) -> JsonResult {
let requesting_user = headers.user; let requesting_user = headers.user;
let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &mut conn).await { let emergency_access = match EmergencyAccess::find_by_uuid(emer_id, &conn).await {
Some(emer) => emer, Some(emer) => emer,
None => err!("Emergency access not valid."), None => err!("Emergency access not valid."),
}; };
@ -696,12 +692,12 @@ async fn policies_emergency_access(emer_id: &str, headers: Headers, mut conn: Db
err!("Emergency access not valid.") err!("Emergency access not valid.")
} }
let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Grantor user not found."), None => err!("Grantor user not found."),
}; };
let policies = OrgPolicy::find_confirmed_by_user(&grantor_user.uuid, &mut conn); let policies = OrgPolicy::find_confirmed_by_user(&grantor_user.uuid, &conn);
let policies_json: Vec<Value> = policies.await.iter().map(OrgPolicy::to_json).collect(); let policies_json: Vec<Value> = policies.await.iter().map(OrgPolicy::to_json).collect();
Ok(Json(json!({ Ok(Json(json!({
@ -735,8 +731,8 @@ pub async fn emergency_request_timeout_job(pool: DbPool) {
return; return;
} }
if let Ok(mut conn) = pool.get().await { if let Ok(conn) = pool.get().await {
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&mut conn).await; let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&conn).await;
if emergency_access_list.is_empty() { if emergency_access_list.is_empty() {
debug!("No emergency request timeout to approve"); debug!("No emergency request timeout to approve");
@ -750,18 +746,18 @@ pub async fn emergency_request_timeout_job(pool: DbPool) {
if recovery_allowed_at.le(&now) { if recovery_allowed_at.le(&now) {
// Only update the access status // Only update the access status
// Updating the whole record could cause issues when the emergency_notification_reminder_job is also active // Updating the whole record could cause issues when the emergency_notification_reminder_job is also active
emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &mut conn) emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn)
.await .await
.expect("Unable to update emergency access status"); .expect("Unable to update emergency access status");
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
// get grantor user to send Accepted email // get grantor user to send Accepted email
let grantor_user = let grantor_user =
User::find_by_uuid(&emer.grantor_uuid, &mut conn).await.expect("Grantor user not found"); User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found");
// get grantee user to send Accepted email // get grantee user to send Accepted email
let grantee_user = let grantee_user =
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &mut conn) User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &conn)
.await .await
.expect("Grantee user not found"); .expect("Grantee user not found");
@ -790,8 +786,8 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) {
return; return;
} }
if let Ok(mut conn) = pool.get().await { if let Ok(conn) = pool.get().await {
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&mut conn).await; let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&conn).await;
if emergency_access_list.is_empty() { if emergency_access_list.is_empty() {
debug!("No emergency request reminder notification to send"); debug!("No emergency request reminder notification to send");
@ -812,18 +808,18 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) {
if final_recovery_reminder_at.le(&now) && next_recovery_reminder_at.le(&now) { if final_recovery_reminder_at.le(&now) && next_recovery_reminder_at.le(&now) {
// Only update the last notification date // Only update the last notification date
// Updating the whole record could cause issues when the emergency_request_timeout_job is also active // Updating the whole record could cause issues when the emergency_request_timeout_job is also active
emer.update_last_notification_date_and_save(&now, &mut conn) emer.update_last_notification_date_and_save(&now, &conn)
.await .await
.expect("Unable to update emergency access notification date"); .expect("Unable to update emergency access notification date");
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
// get grantor user to send Accepted email // get grantor user to send Accepted email
let grantor_user = let grantor_user =
User::find_by_uuid(&emer.grantor_uuid, &mut conn).await.expect("Grantor user not found"); User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found");
// get grantee user to send Accepted email // get grantee user to send Accepted email
let grantee_user = let grantee_user =
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &mut conn) User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &conn)
.await .await
.expect("Grantee user not found"); .expect("Grantee user not found");

Datei anzeigen

@ -32,7 +32,7 @@ struct EventRange {
// Upstream: https://github.com/bitwarden/server/blob/9ecf69d9cabce732cf2c57976dd9afa5728578fb/src/Api/Controllers/EventsController.cs#LL84C35-L84C41 // Upstream: https://github.com/bitwarden/server/blob/9ecf69d9cabce732cf2c57976dd9afa5728578fb/src/Api/Controllers/EventsController.cs#LL84C35-L84C41
#[get("/organizations/<org_id>/events?<data..>")] #[get("/organizations/<org_id>/events?<data..>")]
async fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders, mut conn: DbConn) -> JsonResult { async fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
// Return an empty vec when we org events are disabled. // Return an empty vec when we org events are disabled.
// This prevents client errors // This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() { let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
@ -45,7 +45,7 @@ async fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders,
parse_date(&data.end) parse_date(&data.end)
}; };
Event::find_by_organization_uuid(org_id, &start_date, &end_date, &mut conn) Event::find_by_organization_uuid(org_id, &start_date, &end_date, &conn)
.await .await
.iter() .iter()
.map(|e| e.to_json()) .map(|e| e.to_json())
@ -60,14 +60,14 @@ async fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders,
} }
#[get("/ciphers/<cipher_id>/events?<data..>")] #[get("/ciphers/<cipher_id>/events?<data..>")]
async fn get_cipher_events(cipher_id: &str, data: EventRange, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_cipher_events(cipher_id: &str, data: EventRange, headers: Headers, conn: DbConn) -> JsonResult {
// Return an empty vec when we org events are disabled. // Return an empty vec when we org events are disabled.
// This prevents client errors // This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() { let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
Vec::with_capacity(0) Vec::with_capacity(0)
} else { } else {
let mut events_json = Vec::with_capacity(0); let mut events_json = Vec::with_capacity(0);
if UserOrganization::user_has_ge_admin_access_to_cipher(&headers.user.uuid, cipher_id, &mut conn).await { if UserOrganization::user_has_ge_admin_access_to_cipher(&headers.user.uuid, cipher_id, &conn).await {
let start_date = parse_date(&data.start); let start_date = parse_date(&data.start);
let end_date = if let Some(before_date) = &data.continuation_token { let end_date = if let Some(before_date) = &data.continuation_token {
parse_date(before_date) parse_date(before_date)
@ -75,7 +75,7 @@ async fn get_cipher_events(cipher_id: &str, data: EventRange, headers: Headers,
parse_date(&data.end) parse_date(&data.end)
}; };
events_json = Event::find_by_cipher_uuid(cipher_id, &start_date, &end_date, &mut conn) events_json = Event::find_by_cipher_uuid(cipher_id, &start_date, &end_date, &conn)
.await .await
.iter() .iter()
.map(|e| e.to_json()) .map(|e| e.to_json())
@ -97,7 +97,7 @@ async fn get_user_events(
user_org_id: &str, user_org_id: &str,
data: EventRange, data: EventRange,
_headers: AdminHeaders, _headers: AdminHeaders,
mut conn: DbConn, conn: DbConn,
) -> JsonResult { ) -> JsonResult {
// Return an empty vec when we org events are disabled. // Return an empty vec when we org events are disabled.
// This prevents client errors // This prevents client errors
@ -111,7 +111,7 @@ async fn get_user_events(
parse_date(&data.end) parse_date(&data.end)
}; };
Event::find_by_org_and_user_org(org_id, user_org_id, &start_date, &end_date, &mut conn) Event::find_by_org_and_user_org(org_id, user_org_id, &start_date, &end_date, &conn)
.await .await
.iter() .iter()
.map(|e| e.to_json()) .map(|e| e.to_json())
@ -161,7 +161,7 @@ struct EventCollection {
// https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Events/Controllers/CollectController.cs // https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Events/Controllers/CollectController.cs
// https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Services/Implementations/EventService.cs // https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Services/Implementations/EventService.cs
#[post("/collect", format = "application/json", data = "<data>")] #[post("/collect", format = "application/json", data = "<data>")]
async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Headers, conn: DbConn) -> EmptyResult {
if !CONFIG.org_events_enabled() { if !CONFIG.org_events_enabled() {
return Ok(()); return Ok(());
} }
@ -176,7 +176,7 @@ async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Head
headers.device.atype, headers.device.atype,
Some(event_date), Some(event_date),
&headers.ip.ip, &headers.ip.ip,
&mut conn, &conn,
) )
.await; .await;
} }
@ -190,14 +190,14 @@ async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Head
headers.device.atype, headers.device.atype,
Some(event_date), Some(event_date),
&headers.ip.ip, &headers.ip.ip,
&mut conn, &conn,
) )
.await; .await;
} }
} }
_ => { _ => {
if let Some(cipher_uuid) = &event.CipherId { if let Some(cipher_uuid) = &event.CipherId {
if let Some(cipher) = Cipher::find_by_uuid(cipher_uuid, &mut conn).await { if let Some(cipher) = Cipher::find_by_uuid(cipher_uuid, &conn).await {
if let Some(org_uuid) = cipher.organization_uuid { if let Some(org_uuid) = cipher.organization_uuid {
_log_event( _log_event(
event.Type, event.Type,
@ -207,7 +207,7 @@ async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Head
headers.device.atype, headers.device.atype,
Some(event_date), Some(event_date),
&headers.ip.ip, &headers.ip.ip,
&mut conn, &conn,
) )
.await; .await;
} }
@ -219,7 +219,7 @@ async fn post_events_collect(data: JsonUpcaseVec<EventCollection>, headers: Head
Ok(()) Ok(())
} }
pub async fn log_user_event(event_type: i32, user_uuid: &str, device_type: i32, ip: &IpAddr, conn: &mut DbConn) { pub async fn log_user_event(event_type: i32, user_uuid: &str, device_type: i32, ip: &IpAddr, conn: &DbConn) {
if !CONFIG.org_events_enabled() { if !CONFIG.org_events_enabled() {
return; return;
} }
@ -232,7 +232,7 @@ async fn _log_user_event(
device_type: i32, device_type: i32,
event_date: Option<NaiveDateTime>, event_date: Option<NaiveDateTime>,
ip: &IpAddr, ip: &IpAddr,
conn: &mut DbConn, conn: &DbConn,
) { ) {
let orgs = UserOrganization::get_org_uuid_by_user(user_uuid, conn).await; let orgs = UserOrganization::get_org_uuid_by_user(user_uuid, conn).await;
let mut events: Vec<Event> = Vec::with_capacity(orgs.len() + 1); // We need an event per org and one without an org let mut events: Vec<Event> = Vec::with_capacity(orgs.len() + 1); // We need an event per org and one without an org
@ -266,7 +266,7 @@ pub async fn log_event(
act_user_uuid: String, act_user_uuid: String,
device_type: i32, device_type: i32,
ip: &IpAddr, ip: &IpAddr,
conn: &mut DbConn, conn: &DbConn,
) { ) {
if !CONFIG.org_events_enabled() { if !CONFIG.org_events_enabled() {
return; return;
@ -283,7 +283,7 @@ async fn _log_event(
device_type: i32, device_type: i32,
event_date: Option<NaiveDateTime>, event_date: Option<NaiveDateTime>,
ip: &IpAddr, ip: &IpAddr,
conn: &mut DbConn, conn: &DbConn,
) { ) {
// Create a new empty event // Create a new empty event
let mut event = Event::new(event_type, event_date); let mut event = Event::new(event_type, event_date);
@ -328,8 +328,8 @@ pub async fn event_cleanup_job(pool: DbPool) {
return; return;
} }
if let Ok(mut conn) = pool.get().await { if let Ok(conn) = pool.get().await {
Event::clean_events(&mut conn).await.ok(); Event::clean_events(&conn).await.ok();
} else { } else {
error!("Failed to get DB connection while trying to cleanup the events table") error!("Failed to get DB connection while trying to cleanup the events table")
} }

Datei anzeigen

@ -12,8 +12,8 @@ pub fn routes() -> Vec<rocket::Route> {
} }
#[get("/folders")] #[get("/folders")]
async fn get_folders(headers: Headers, mut conn: DbConn) -> Json<Value> { async fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> {
let folders = Folder::find_by_user(&headers.user.uuid, &mut conn).await; let folders = Folder::find_by_user(&headers.user.uuid, &conn).await;
let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect(); let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect();
Json(json!({ Json(json!({
@ -24,8 +24,8 @@ async fn get_folders(headers: Headers, mut conn: DbConn) -> Json<Value> {
} }
#[get("/folders/<uuid>")] #[get("/folders/<uuid>")]
async fn get_folder(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_folder(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
let folder = match Folder::find_by_uuid(uuid, &mut conn).await { let folder = match Folder::find_by_uuid(uuid, &conn).await {
Some(folder) => folder, Some(folder) => folder,
_ => err!("Invalid folder"), _ => err!("Invalid folder"),
}; };
@ -44,13 +44,13 @@ pub struct FolderData {
} }
#[post("/folders", data = "<data>")] #[post("/folders", data = "<data>")]
async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
let data: FolderData = data.into_inner().data; let data: FolderData = data.into_inner().data;
let mut folder = Folder::new(headers.user.uuid, data.Name); let mut folder = Folder::new(headers.user.uuid, data.Name);
folder.save(&mut conn).await?; folder.save(&conn).await?;
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid, &mut conn).await; nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid, &conn).await;
Ok(Json(folder.to_json())) Ok(Json(folder.to_json()))
} }
@ -71,12 +71,12 @@ async fn put_folder(
uuid: &str, uuid: &str,
data: JsonUpcase<FolderData>, data: JsonUpcase<FolderData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let data: FolderData = data.into_inner().data; let data: FolderData = data.into_inner().data;
let mut folder = match Folder::find_by_uuid(uuid, &mut conn).await { let mut folder = match Folder::find_by_uuid(uuid, &conn).await {
Some(folder) => folder, Some(folder) => folder,
_ => err!("Invalid folder"), _ => err!("Invalid folder"),
}; };
@ -87,8 +87,8 @@ async fn put_folder(
folder.name = data.Name; folder.name = data.Name;
folder.save(&mut conn).await?; folder.save(&conn).await?;
nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid, &mut conn).await; nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid, &conn).await;
Ok(Json(folder.to_json())) Ok(Json(folder.to_json()))
} }
@ -99,8 +99,8 @@ async fn delete_folder_post(uuid: &str, headers: Headers, conn: DbConn, nt: Noti
} }
#[delete("/folders/<uuid>")] #[delete("/folders/<uuid>")]
async fn delete_folder(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_folder(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let folder = match Folder::find_by_uuid(uuid, &mut conn).await { let folder = match Folder::find_by_uuid(uuid, &conn).await {
Some(folder) => folder, Some(folder) => folder,
_ => err!("Invalid folder"), _ => err!("Invalid folder"),
}; };
@ -110,8 +110,8 @@ async fn delete_folder(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notif
} }
// Delete the actual folder entry // Delete the actual folder entry
folder.delete(&mut conn).await?; folder.delete(&conn).await?;
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid, &mut conn).await; nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid, &conn).await;
Ok(()) Ok(())
} }

Datei anzeigen

@ -108,7 +108,7 @@ struct EquivDomainData {
async fn post_eq_domains( async fn post_eq_domains(
data: JsonUpcase<EquivDomainData>, data: JsonUpcase<EquivDomainData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let data: EquivDomainData = data.into_inner().data; let data: EquivDomainData = data.into_inner().data;
@ -122,7 +122,7 @@ async fn post_eq_domains(
user.excluded_globals = to_string(&excluded_globals).unwrap_or_else(|_| "[]".to_string()); user.excluded_globals = to_string(&excluded_globals).unwrap_or_else(|_| "[]".to_string());
user.equivalent_domains = to_string(&equivalent_domains).unwrap_or_else(|_| "[]".to_string()); user.equivalent_domains = to_string(&equivalent_domains).unwrap_or_else(|_| "[]".to_string());
user.save(&mut conn).await?; user.save(&conn).await?;
nt.send_user_update(UpdateType::SyncSettings, &user).await; nt.send_user_update(UpdateType::SyncSettings, &user).await;

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@ -43,7 +43,7 @@ struct OrgImportData {
} }
#[post("/public/organization/import", data = "<data>")] #[post("/public/organization/import", data = "<data>")]
async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, mut conn: DbConn) -> EmptyResult { async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, conn: DbConn) -> EmptyResult {
// Most of the logic for this function can be found here // Most of the logic for this function can be found here
// https://github.com/bitwarden/server/blob/fd892b2ff4547648a276734fb2b14a8abae2c6f5/src/Core/Services/Implementations/OrganizationService.cs#L1797 // https://github.com/bitwarden/server/blob/fd892b2ff4547648a276734fb2b14a8abae2c6f5/src/Core/Services/Implementations/OrganizationService.cs#L1797
@ -53,15 +53,13 @@ async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, mut co
for user_data in &data.Members { for user_data in &data.Members {
if user_data.Deleted { if user_data.Deleted {
// If user is marked for deletion and it exists, revoke it // If user is marked for deletion and it exists, revoke it
if let Some(mut user_org) = if let Some(mut user_org) = UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn).await
UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &mut conn).await
{ {
// Only revoke a user if it is not the last confirmed owner // Only revoke a user if it is not the last confirmed owner
let revoked = if user_org.atype == UserOrgType::Owner let revoked = if user_org.atype == UserOrgType::Owner
&& user_org.status == UserOrgStatus::Confirmed as i32 && user_org.status == UserOrgStatus::Confirmed as i32
{ {
if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &mut conn).await if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &conn).await <= 1
<= 1
{ {
warn!("Can't revoke the last owner"); warn!("Can't revoke the last owner");
false false
@ -74,30 +72,30 @@ async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, mut co
let ext_modified = user_org.set_external_id(Some(user_data.ExternalId.clone())); let ext_modified = user_org.set_external_id(Some(user_data.ExternalId.clone()));
if revoked || ext_modified { if revoked || ext_modified {
user_org.save(&mut conn).await?; user_org.save(&conn).await?;
} }
} }
// If user is part of the organization, restore it // If user is part of the organization, restore it
} else if let Some(mut user_org) = } else if let Some(mut user_org) =
UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &mut conn).await UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn).await
{ {
let restored = user_org.restore(); let restored = user_org.restore();
let ext_modified = user_org.set_external_id(Some(user_data.ExternalId.clone())); let ext_modified = user_org.set_external_id(Some(user_data.ExternalId.clone()));
if restored || ext_modified { if restored || ext_modified {
user_org.save(&mut conn).await?; user_org.save(&conn).await?;
} }
} else { } else {
// If user is not part of the organization // If user is not part of the organization
let user = match User::find_by_mail(&user_data.Email, &mut conn).await { let user = match User::find_by_mail(&user_data.Email, &conn).await {
Some(user) => user, // exists in vaultwarden Some(user) => user, // exists in vaultwarden
None => { None => {
// User does not exist yet // User does not exist yet
let mut new_user = User::new(user_data.Email.clone()); let mut new_user = User::new(user_data.Email.clone());
new_user.save(&mut conn).await?; new_user.save(&conn).await?;
if !CONFIG.mail_enabled() { if !CONFIG.mail_enabled() {
let invitation = Invitation::new(&new_user.email); let invitation = Invitation::new(&new_user.email);
invitation.save(&mut conn).await?; invitation.save(&conn).await?;
} }
new_user new_user
} }
@ -114,10 +112,10 @@ async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, mut co
new_org_user.atype = UserOrgType::User as i32; new_org_user.atype = UserOrgType::User as i32;
new_org_user.status = user_org_status; new_org_user.status = user_org_status;
new_org_user.save(&mut conn).await?; new_org_user.save(&conn).await?;
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
let (org_name, org_email) = match Organization::find_by_uuid(&org_id, &mut conn).await { let (org_name, org_email) = match Organization::find_by_uuid(&org_id, &conn).await {
Some(org) => (org.name, org.billing_email), Some(org) => (org.name, org.billing_email),
None => err!("Error looking up organization"), None => err!("Error looking up organization"),
}; };
@ -137,23 +135,22 @@ async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, mut co
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
for group_data in &data.Groups { for group_data in &data.Groups {
let group_uuid = match Group::find_by_external_id(&group_data.ExternalId, &mut conn).await { let group_uuid = match Group::find_by_external_id(&group_data.ExternalId, &conn).await {
Some(group) => group.uuid, Some(group) => group.uuid,
None => { None => {
let mut group = let mut group =
Group::new(org_id.clone(), group_data.Name.clone(), false, Some(group_data.ExternalId.clone())); Group::new(org_id.clone(), group_data.Name.clone(), false, Some(group_data.ExternalId.clone()));
group.save(&mut conn).await?; group.save(&conn).await?;
group.uuid group.uuid
} }
}; };
GroupUser::delete_all_by_group(&group_uuid, &mut conn).await?; GroupUser::delete_all_by_group(&group_uuid, &conn).await?;
for ext_id in &group_data.MemberExternalIds { for ext_id in &group_data.MemberExternalIds {
if let Some(user_org) = UserOrganization::find_by_external_id_and_org(ext_id, &org_id, &mut conn).await if let Some(user_org) = UserOrganization::find_by_external_id_and_org(ext_id, &org_id, &conn).await {
{
let mut group_user = GroupUser::new(group_uuid.clone(), user_org.uuid.clone()); let mut group_user = GroupUser::new(group_uuid.clone(), user_org.uuid.clone());
group_user.save(&mut conn).await?; group_user.save(&conn).await?;
} }
} }
} }
@ -165,20 +162,19 @@ async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, mut co
if data.OverwriteExisting { if data.OverwriteExisting {
// Generate a HashSet to quickly verify if a member is listed or not. // Generate a HashSet to quickly verify if a member is listed or not.
let sync_members: HashSet<String> = data.Members.into_iter().map(|m| m.ExternalId).collect(); let sync_members: HashSet<String> = data.Members.into_iter().map(|m| m.ExternalId).collect();
for user_org in UserOrganization::find_by_org(&org_id, &mut conn).await { for user_org in UserOrganization::find_by_org(&org_id, &conn).await {
if let Some(ref user_external_id) = user_org.external_id { if let Some(ref user_external_id) = user_org.external_id {
if !sync_members.contains(user_external_id) { if !sync_members.contains(user_external_id) {
if user_org.atype == UserOrgType::Owner && user_org.status == UserOrgStatus::Confirmed as i32 { if user_org.atype == UserOrgType::Owner && user_org.status == UserOrgStatus::Confirmed as i32 {
// Removing owner, check that there is at least one other confirmed owner // Removing owner, check that there is at least one other confirmed owner
if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &mut conn) if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &conn).await
.await
<= 1 <= 1
{ {
warn!("Can't delete the last owner"); warn!("Can't delete the last owner");
continue; continue;
} }
} }
user_org.delete(&mut conn).await?; user_org.delete(&conn).await?;
} }
} }
} }

Datei anzeigen

@ -39,8 +39,8 @@ pub fn routes() -> Vec<rocket::Route> {
pub async fn purge_sends(pool: DbPool) { pub async fn purge_sends(pool: DbPool) {
debug!("Purging sends"); debug!("Purging sends");
if let Ok(mut conn) = pool.get().await { if let Ok(conn) = pool.get().await {
Send::purge(&mut conn).await; Send::purge(&conn).await;
} else { } else {
error!("Failed to get DB connection while purging sends") error!("Failed to get DB connection while purging sends")
} }
@ -74,7 +74,7 @@ struct SendData {
/// ///
/// There is also a Vaultwarden-specific `sends_allowed` config setting that /// There is also a Vaultwarden-specific `sends_allowed` config setting that
/// controls this policy globally. /// controls this policy globally.
async fn enforce_disable_send_policy(headers: &Headers, conn: &mut DbConn) -> EmptyResult { async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult {
let user_uuid = &headers.user.uuid; let user_uuid = &headers.user.uuid;
if !CONFIG.sends_allowed() if !CONFIG.sends_allowed()
|| OrgPolicy::is_applicable_to_user(user_uuid, OrgPolicyType::DisableSend, None, conn).await || OrgPolicy::is_applicable_to_user(user_uuid, OrgPolicyType::DisableSend, None, conn).await
@ -90,7 +90,7 @@ async fn enforce_disable_send_policy(headers: &Headers, conn: &mut DbConn) -> Em
/// but is allowed to remove this option from an existing Send. /// but is allowed to remove this option from an existing Send.
/// ///
/// Ref: https://bitwarden.com/help/article/policies/#send-options /// Ref: https://bitwarden.com/help/article/policies/#send-options
async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &mut DbConn) -> EmptyResult { async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &DbConn) -> EmptyResult {
let user_uuid = &headers.user.uuid; let user_uuid = &headers.user.uuid;
let hide_email = data.HideEmail.unwrap_or(false); let hide_email = data.HideEmail.unwrap_or(false);
if hide_email && OrgPolicy::is_hide_email_disabled(user_uuid, conn).await { if hide_email && OrgPolicy::is_hide_email_disabled(user_uuid, conn).await {
@ -142,8 +142,8 @@ fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
} }
#[get("/sends")] #[get("/sends")]
async fn get_sends(headers: Headers, mut conn: DbConn) -> Json<Value> { async fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> {
let sends = Send::find_by_user(&headers.user.uuid, &mut conn); let sends = Send::find_by_user(&headers.user.uuid, &conn);
let sends_json: Vec<Value> = sends.await.iter().map(|s| s.to_json()).collect(); let sends_json: Vec<Value> = sends.await.iter().map(|s| s.to_json()).collect();
Json(json!({ Json(json!({
@ -154,8 +154,8 @@ async fn get_sends(headers: Headers, mut conn: DbConn) -> Json<Value> {
} }
#[get("/sends/<uuid>")] #[get("/sends/<uuid>")]
async fn get_send(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_send(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
let send = match Send::find_by_uuid(uuid, &mut conn).await { let send = match Send::find_by_uuid(uuid, &conn).await {
Some(send) => send, Some(send) => send,
None => err!("Send not found"), None => err!("Send not found"),
}; };
@ -168,24 +168,24 @@ async fn get_send(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult
} }
#[post("/sends", data = "<data>")] #[post("/sends", data = "<data>")]
async fn post_send(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &mut conn).await?; enforce_disable_send_policy(&headers, &conn).await?;
let data: SendData = data.into_inner().data; let data: SendData = data.into_inner().data;
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?; enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
if data.Type == SendType::File as i32 { if data.Type == SendType::File as i32 {
err!("File sends should use /api/sends/file") err!("File sends should use /api/sends/file")
} }
let mut send = create_send(data, headers.user.uuid)?; let mut send = create_send(data, headers.user.uuid)?;
send.save(&mut conn).await?; send.save(&conn).await?;
nt.send_send_update( nt.send_send_update(
UpdateType::SyncSendCreate, UpdateType::SyncSendCreate,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&conn).await,
&headers.device.uuid, &headers.device.uuid,
&mut conn, &conn,
) )
.await; .await;
@ -207,8 +207,8 @@ struct UploadDataV2<'f> {
// This method still exists to support older clients, probably need to remove it sometime. // This method still exists to support older clients, probably need to remove it sometime.
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L164-L167 // Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L164-L167
#[post("/sends/file", format = "multipart/form-data", data = "<data>")] #[post("/sends/file", format = "multipart/form-data", data = "<data>")]
async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &mut conn).await?; enforce_disable_send_policy(&headers, &conn).await?;
let UploadData { let UploadData {
model, model,
@ -216,12 +216,12 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
} = data.into_inner(); } = data.into_inner();
let model = model.into_inner().data; let model = model.into_inner().data;
enforce_disable_hide_email_policy(&model, &headers, &mut conn).await?; enforce_disable_hide_email_policy(&model, &headers, &conn).await?;
let size_limit = match CONFIG.user_attachment_limit() { let size_limit = match CONFIG.user_attachment_limit() {
Some(0) => err!("File uploads are disabled"), Some(0) => err!("File uploads are disabled"),
Some(limit_kb) => { Some(limit_kb) => {
let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &mut conn).await; let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn).await;
if left <= 0 { if left <= 0 {
err!("Attachment storage limit reached! Delete some attachments to free up space") err!("Attachment storage limit reached! Delete some attachments to free up space")
} }
@ -258,13 +258,13 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
send.data = serde_json::to_string(&data_value)?; send.data = serde_json::to_string(&data_value)?;
// Save the changes in the database // Save the changes in the database
send.save(&mut conn).await?; send.save(&conn).await?;
nt.send_send_update( nt.send_send_update(
UpdateType::SyncSendCreate, UpdateType::SyncSendCreate,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&conn).await,
&headers.device.uuid, &headers.device.uuid,
&mut conn, &conn,
) )
.await; .await;
@ -273,8 +273,8 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L190 // Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L190
#[post("/sends/file/v2", data = "<data>")] #[post("/sends/file/v2", data = "<data>")]
async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn) -> JsonResult {
enforce_disable_send_policy(&headers, &mut conn).await?; enforce_disable_send_policy(&headers, &conn).await?;
let data = data.into_inner().data; let data = data.into_inner().data;
@ -282,7 +282,7 @@ async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, mut con
err!("Send content is not a file"); err!("Send content is not a file");
} }
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?; enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
let file_length = match &data.FileLength { let file_length = match &data.FileLength {
Some(m) => Some(m.into_i32()?), Some(m) => Some(m.into_i32()?),
@ -292,7 +292,7 @@ async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, mut con
let size_limit = match CONFIG.user_attachment_limit() { let size_limit = match CONFIG.user_attachment_limit() {
Some(0) => err!("File uploads are disabled"), Some(0) => err!("File uploads are disabled"),
Some(limit_kb) => { Some(limit_kb) => {
let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &mut conn).await; let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn).await;
if left <= 0 { if left <= 0 {
err!("Attachment storage limit reached! Delete some attachments to free up space") err!("Attachment storage limit reached! Delete some attachments to free up space")
} }
@ -316,7 +316,7 @@ async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, mut con
o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(file_length.unwrap()))); o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(file_length.unwrap())));
} }
send.data = serde_json::to_string(&data_value)?; send.data = serde_json::to_string(&data_value)?;
send.save(&mut conn).await?; send.save(&conn).await?;
Ok(Json(json!({ Ok(Json(json!({
"fileUploadType": 0, // 0 == Direct | 1 == Azure "fileUploadType": 0, // 0 == Direct | 1 == Azure
@ -333,14 +333,14 @@ async fn post_send_file_v2_data(
file_id: &str, file_id: &str,
data: Form<UploadDataV2<'_>>, data: Form<UploadDataV2<'_>>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
enforce_disable_send_policy(&headers, &mut conn).await?; enforce_disable_send_policy(&headers, &conn).await?;
let mut data = data.into_inner(); let mut data = data.into_inner();
let Some(send) = Send::find_by_uuid(send_uuid, &mut conn).await else { let Some(send) = Send::find_by_uuid(send_uuid, &conn).await else {
err!("Send not found. Unable to save the file.") err!("Send not found. Unable to save the file.")
}; };
@ -362,9 +362,9 @@ async fn post_send_file_v2_data(
nt.send_send_update( nt.send_send_update(
UpdateType::SyncSendCreate, UpdateType::SyncSendCreate,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&conn).await,
&headers.device.uuid, &headers.device.uuid,
&mut conn, &conn,
) )
.await; .await;
@ -381,11 +381,11 @@ pub struct SendAccessData {
async fn post_access( async fn post_access(
access_id: &str, access_id: &str,
data: JsonUpcase<SendAccessData>, data: JsonUpcase<SendAccessData>,
mut conn: DbConn, conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let mut send = match Send::find_by_access_id(access_id, &mut conn).await { let mut send = match Send::find_by_access_id(access_id, &conn).await {
Some(s) => s, Some(s) => s,
None => err_code!(SEND_INACCESSIBLE_MSG, 404), None => err_code!(SEND_INACCESSIBLE_MSG, 404),
}; };
@ -423,18 +423,18 @@ async fn post_access(
send.access_count += 1; send.access_count += 1;
} }
send.save(&mut conn).await?; send.save(&conn).await?;
nt.send_send_update( nt.send_send_update(
UpdateType::SyncSendUpdate, UpdateType::SyncSendUpdate,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&conn).await,
&String::from("00000000-0000-0000-0000-000000000000"), &String::from("00000000-0000-0000-0000-000000000000"),
&mut conn, &conn,
) )
.await; .await;
Ok(Json(send.to_json_access(&mut conn).await)) Ok(Json(send.to_json_access(&conn).await))
} }
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")] #[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
@ -443,10 +443,10 @@ async fn post_access_file(
file_id: &str, file_id: &str,
data: JsonUpcase<SendAccessData>, data: JsonUpcase<SendAccessData>,
host: Host, host: Host,
mut conn: DbConn, conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let mut send = match Send::find_by_uuid(send_id, &mut conn).await { let mut send = match Send::find_by_uuid(send_id, &conn).await {
Some(s) => s, Some(s) => s,
None => err_code!(SEND_INACCESSIBLE_MSG, 404), None => err_code!(SEND_INACCESSIBLE_MSG, 404),
}; };
@ -481,14 +481,14 @@ async fn post_access_file(
send.access_count += 1; send.access_count += 1;
send.save(&mut conn).await?; send.save(&conn).await?;
nt.send_send_update( nt.send_send_update(
UpdateType::SyncSendUpdate, UpdateType::SyncSendUpdate,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&conn).await,
&String::from("00000000-0000-0000-0000-000000000000"), &String::from("00000000-0000-0000-0000-000000000000"),
&mut conn, &conn,
) )
.await; .await;
@ -512,19 +512,13 @@ async fn download_send(send_id: SafeString, file_id: SafeString, t: &str) -> Opt
} }
#[put("/sends/<id>", data = "<data>")] #[put("/sends/<id>", data = "<data>")]
async fn put_send( async fn put_send(id: &str, data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
id: &str, enforce_disable_send_policy(&headers, &conn).await?;
data: JsonUpcase<SendData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
enforce_disable_send_policy(&headers, &mut conn).await?;
let data: SendData = data.into_inner().data; let data: SendData = data.into_inner().data;
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?; enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
let mut send = match Send::find_by_uuid(id, &mut conn).await { let mut send = match Send::find_by_uuid(id, &conn).await {
Some(s) => s, Some(s) => s,
None => err!("Send not found"), None => err!("Send not found"),
}; };
@ -571,13 +565,13 @@ async fn put_send(
send.set_password(Some(&password)); send.set_password(Some(&password));
} }
send.save(&mut conn).await?; send.save(&conn).await?;
nt.send_send_update( nt.send_send_update(
UpdateType::SyncSendUpdate, UpdateType::SyncSendUpdate,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&conn).await,
&headers.device.uuid, &headers.device.uuid,
&mut conn, &conn,
) )
.await; .await;
@ -585,8 +579,8 @@ async fn put_send(
} }
#[delete("/sends/<id>")] #[delete("/sends/<id>")]
async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_send(id: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let send = match Send::find_by_uuid(id, &mut conn).await { let send = match Send::find_by_uuid(id, &conn).await {
Some(s) => s, Some(s) => s,
None => err!("Send not found"), None => err!("Send not found"),
}; };
@ -595,13 +589,13 @@ async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_
err!("Send is not owned by user") err!("Send is not owned by user")
} }
send.delete(&mut conn).await?; send.delete(&conn).await?;
nt.send_send_update( nt.send_send_update(
UpdateType::SyncSendDelete, UpdateType::SyncSendDelete,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&conn).await,
&headers.device.uuid, &headers.device.uuid,
&mut conn, &conn,
) )
.await; .await;
@ -609,10 +603,10 @@ async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_
} }
#[put("/sends/<id>/remove-password")] #[put("/sends/<id>/remove-password")]
async fn put_remove_password(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn put_remove_password(id: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &mut conn).await?; enforce_disable_send_policy(&headers, &conn).await?;
let mut send = match Send::find_by_uuid(id, &mut conn).await { let mut send = match Send::find_by_uuid(id, &conn).await {
Some(s) => s, Some(s) => s,
None => err!("Send not found"), None => err!("Send not found"),
}; };
@ -622,13 +616,13 @@ async fn put_remove_password(id: &str, headers: Headers, mut conn: DbConn, nt: N
} }
send.set_password(None); send.set_password(None);
send.save(&mut conn).await?; send.save(&conn).await?;
nt.send_send_update( nt.send_send_update(
UpdateType::SyncSendUpdate, UpdateType::SyncSendUpdate,
&send, &send,
&send.update_users_revision(&mut conn).await, &send.update_users_revision(&conn).await,
&headers.device.uuid, &headers.device.uuid,
&mut conn, &conn,
) )
.await; .await;

Datei anzeigen

@ -22,7 +22,7 @@ pub fn routes() -> Vec<Route> {
} }
#[post("/two-factor/get-authenticator", data = "<data>")] #[post("/two-factor/get-authenticator", data = "<data>")]
async fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: PasswordData = data.into_inner().data; let data: PasswordData = data.into_inner().data;
let user = headers.user; let user = headers.user;
@ -31,7 +31,7 @@ async fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers
} }
let type_ = TwoFactorType::Authenticator as i32; let type_ = TwoFactorType::Authenticator as i32;
let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await; let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await;
let (enabled, key) = match twofactor { let (enabled, key) = match twofactor {
Some(tf) => (true, tf.data), Some(tf) => (true, tf.data),
@ -57,7 +57,7 @@ struct EnableAuthenticatorData {
async fn activate_authenticator( async fn activate_authenticator(
data: JsonUpcase<EnableAuthenticatorData>, data: JsonUpcase<EnableAuthenticatorData>,
headers: Headers, headers: Headers,
mut conn: DbConn, conn: DbConn,
) -> JsonResult { ) -> JsonResult {
let data: EnableAuthenticatorData = data.into_inner().data; let data: EnableAuthenticatorData = data.into_inner().data;
let password_hash = data.MasterPasswordHash; let password_hash = data.MasterPasswordHash;
@ -81,11 +81,11 @@ async fn activate_authenticator(
} }
// Validate the token provided with the key, and save new twofactor // Validate the token provided with the key, and save new twofactor
validate_totp_code(&user.uuid, &token, &key.to_uppercase(), &headers.ip, &mut conn).await?; validate_totp_code(&user.uuid, &token, &key.to_uppercase(), &headers.ip, &conn).await?;
_generate_recover_code(&mut user, &mut conn).await; _generate_recover_code(&mut user, &conn).await;
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await; log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
Ok(Json(json!({ Ok(Json(json!({
"Enabled": true, "Enabled": true,
@ -108,7 +108,7 @@ pub async fn validate_totp_code_str(
totp_code: &str, totp_code: &str,
secret: &str, secret: &str,
ip: &ClientIp, ip: &ClientIp,
conn: &mut DbConn, conn: &DbConn,
) -> EmptyResult { ) -> EmptyResult {
if !totp_code.chars().all(char::is_numeric) { if !totp_code.chars().all(char::is_numeric) {
err!("TOTP code is not a number"); err!("TOTP code is not a number");
@ -122,7 +122,7 @@ pub async fn validate_totp_code(
totp_code: &str, totp_code: &str,
secret: &str, secret: &str,
ip: &ClientIp, ip: &ClientIp,
conn: &mut DbConn, conn: &DbConn,
) -> EmptyResult { ) -> EmptyResult {
use totp_lite::{totp_custom, Sha1}; use totp_lite::{totp_custom, Sha1};

Datei anzeigen

@ -92,14 +92,14 @@ impl DuoStatus {
const DISABLED_MESSAGE_DEFAULT: &str = "<To use the global Duo keys, please leave these fields untouched>"; const DISABLED_MESSAGE_DEFAULT: &str = "<To use the global Duo keys, please leave these fields untouched>";
#[post("/two-factor/get-duo", data = "<data>")] #[post("/two-factor/get-duo", data = "<data>")]
async fn get_duo(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_duo(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: PasswordData = data.into_inner().data; let data: PasswordData = data.into_inner().data;
if !headers.user.check_valid_password(&data.MasterPasswordHash) { if !headers.user.check_valid_password(&data.MasterPasswordHash) {
err!("Invalid password"); err!("Invalid password");
} }
let data = get_user_duo_data(&headers.user.uuid, &mut conn).await; let data = get_user_duo_data(&headers.user.uuid, &conn).await;
let (enabled, data) = match data { let (enabled, data) = match data {
DuoStatus::Global(_) => (true, Some(DuoData::secret())), DuoStatus::Global(_) => (true, Some(DuoData::secret())),
@ -155,7 +155,7 @@ fn check_duo_fields_custom(data: &EnableDuoData) -> bool {
} }
#[post("/two-factor/duo", data = "<data>")] #[post("/two-factor/duo", data = "<data>")]
async fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: EnableDuoData = data.into_inner().data; let data: EnableDuoData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -174,11 +174,11 @@ async fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, mut con
let type_ = TwoFactorType::Duo; let type_ = TwoFactorType::Duo;
let twofactor = TwoFactor::new(user.uuid.clone(), type_, data_str); let twofactor = TwoFactor::new(user.uuid.clone(), type_, data_str);
twofactor.save(&mut conn).await?; twofactor.save(&conn).await?;
_generate_recover_code(&mut user, &mut conn).await; _generate_recover_code(&mut user, &conn).await;
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await; log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
Ok(Json(json!({ Ok(Json(json!({
"Enabled": true, "Enabled": true,
@ -228,7 +228,7 @@ const AUTH_PREFIX: &str = "AUTH";
const DUO_PREFIX: &str = "TX"; const DUO_PREFIX: &str = "TX";
const APP_PREFIX: &str = "APP"; const APP_PREFIX: &str = "APP";
async fn get_user_duo_data(uuid: &str, conn: &mut DbConn) -> DuoStatus { async fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus {
let type_ = TwoFactorType::Duo as i32; let type_ = TwoFactorType::Duo as i32;
// If the user doesn't have an entry, disabled // If the user doesn't have an entry, disabled
@ -252,7 +252,7 @@ async fn get_user_duo_data(uuid: &str, conn: &mut DbConn) -> DuoStatus {
} }
// let (ik, sk, ak, host) = get_duo_keys(); // let (ik, sk, ak, host) = get_duo_keys();
async fn get_duo_keys_email(email: &str, conn: &mut DbConn) -> ApiResult<(String, String, String, String)> { async fn get_duo_keys_email(email: &str, conn: &DbConn) -> ApiResult<(String, String, String, String)> {
let data = match User::find_by_mail(email, conn).await { let data = match User::find_by_mail(email, conn).await {
Some(u) => get_user_duo_data(&u.uuid, conn).await.data(), Some(u) => get_user_duo_data(&u.uuid, conn).await.data(),
_ => DuoData::global(), _ => DuoData::global(),
@ -262,7 +262,7 @@ async fn get_duo_keys_email(email: &str, conn: &mut DbConn) -> ApiResult<(String
Ok((data.ik, data.sk, CONFIG.get_duo_akey(), data.host)) Ok((data.ik, data.sk, CONFIG.get_duo_akey(), data.host))
} }
pub async fn generate_duo_signature(email: &str, conn: &mut DbConn) -> ApiResult<(String, String)> { pub async fn generate_duo_signature(email: &str, conn: &DbConn) -> ApiResult<(String, String)> {
let now = Utc::now().timestamp(); let now = Utc::now().timestamp();
let (ik, sk, ak, host) = get_duo_keys_email(email, conn).await?; let (ik, sk, ak, host) = get_duo_keys_email(email, conn).await?;
@ -280,7 +280,7 @@ fn sign_duo_values(key: &str, email: &str, ikey: &str, prefix: &str, expire: i64
format!("{}|{}", cookie, crypto::hmac_sign(key, &cookie)) format!("{}|{}", cookie, crypto::hmac_sign(key, &cookie))
} }
pub async fn validate_duo_login(email: &str, response: &str, conn: &mut DbConn) -> EmptyResult { pub async fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyResult {
// email is as entered by the user, so it needs to be normalized before // email is as entered by the user, so it needs to be normalized before
// comparison with auth_user below. // comparison with auth_user below.
let email = &email.to_lowercase(); let email = &email.to_lowercase();

Datei anzeigen

@ -31,13 +31,13 @@ struct SendEmailLoginData {
/// User is trying to login and wants to use email 2FA. /// User is trying to login and wants to use email 2FA.
/// Does not require Bearer token /// Does not require Bearer token
#[post("/two-factor/send-email-login", data = "<data>")] // JsonResult #[post("/two-factor/send-email-login", data = "<data>")] // JsonResult
async fn send_email_login(data: JsonUpcase<SendEmailLoginData>, mut conn: DbConn) -> EmptyResult { async fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> EmptyResult {
let data: SendEmailLoginData = data.into_inner().data; let data: SendEmailLoginData = data.into_inner().data;
use crate::db::models::User; use crate::db::models::User;
// Get the user // Get the user
let user = match User::find_by_mail(&data.Email, &mut conn).await { let user = match User::find_by_mail(&data.Email, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Username or password is incorrect. Try again."), None => err!("Username or password is incorrect. Try again."),
}; };
@ -51,13 +51,13 @@ async fn send_email_login(data: JsonUpcase<SendEmailLoginData>, mut conn: DbConn
err!("Email 2FA is disabled") err!("Email 2FA is disabled")
} }
send_token(&user.uuid, &mut conn).await?; send_token(&user.uuid, &conn).await?;
Ok(()) Ok(())
} }
/// Generate the token, save the data for later verification and send email to user /// Generate the token, save the data for later verification and send email to user
pub async fn send_token(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult {
let type_ = TwoFactorType::Email as i32; let type_ = TwoFactorType::Email as i32;
let mut twofactor = let mut twofactor =
TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await.map_res("Two factor not found")?; TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await.map_res("Two factor not found")?;
@ -76,7 +76,7 @@ pub async fn send_token(user_uuid: &str, conn: &mut DbConn) -> EmptyResult {
/// When user clicks on Manage email 2FA show the user the related information /// When user clicks on Manage email 2FA show the user the related information
#[post("/two-factor/get-email", data = "<data>")] #[post("/two-factor/get-email", data = "<data>")]
async fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: PasswordData = data.into_inner().data; let data: PasswordData = data.into_inner().data;
let user = headers.user; let user = headers.user;
@ -85,7 +85,7 @@ async fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: D
} }
let (enabled, mfa_email) = let (enabled, mfa_email) =
match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &mut conn).await { match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn).await {
Some(x) => { Some(x) => {
let twofactor_data = EmailTokenData::from_json(&x.data)?; let twofactor_data = EmailTokenData::from_json(&x.data)?;
(true, json!(twofactor_data.email)) (true, json!(twofactor_data.email))
@ -110,7 +110,7 @@ struct SendEmailData {
/// Send a verification email to the specified email address to check whether it exists/belongs to user. /// Send a verification email to the specified email address to check whether it exists/belongs to user.
#[post("/two-factor/send-email", data = "<data>")] #[post("/two-factor/send-email", data = "<data>")]
async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: SendEmailData = data.into_inner().data; let data: SendEmailData = data.into_inner().data;
let user = headers.user; let user = headers.user;
@ -124,8 +124,8 @@ async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, mut conn:
let type_ = TwoFactorType::Email as i32; let type_ = TwoFactorType::Email as i32;
if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await { if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await {
tf.delete(&mut conn).await?; tf.delete(&conn).await?;
} }
let generated_token = crypto::generate_email_token(CONFIG.email_token_size()); let generated_token = crypto::generate_email_token(CONFIG.email_token_size());
@ -133,7 +133,7 @@ async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, mut conn:
// Uses EmailVerificationChallenge as type to show that it's not verified yet. // Uses EmailVerificationChallenge as type to show that it's not verified yet.
let twofactor = TwoFactor::new(user.uuid, TwoFactorType::EmailVerificationChallenge, twofactor_data.to_json()); let twofactor = TwoFactor::new(user.uuid, TwoFactorType::EmailVerificationChallenge, twofactor_data.to_json());
twofactor.save(&mut conn).await?; twofactor.save(&conn).await?;
mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?).await?; mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?).await?;
@ -150,7 +150,7 @@ struct EmailData {
/// Verify email belongs to user and can be used for 2FA email codes. /// Verify email belongs to user and can be used for 2FA email codes.
#[put("/two-factor/email", data = "<data>")] #[put("/two-factor/email", data = "<data>")]
async fn email(data: JsonUpcase<EmailData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: EmailData = data.into_inner().data; let data: EmailData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -160,7 +160,7 @@ async fn email(data: JsonUpcase<EmailData>, headers: Headers, mut conn: DbConn)
let type_ = TwoFactorType::EmailVerificationChallenge as i32; let type_ = TwoFactorType::EmailVerificationChallenge as i32;
let mut twofactor = let mut twofactor =
TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await.map_res("Two factor not found")?; TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await.map_res("Two factor not found")?;
let mut email_data = EmailTokenData::from_json(&twofactor.data)?; let mut email_data = EmailTokenData::from_json(&twofactor.data)?;
@ -176,11 +176,11 @@ async fn email(data: JsonUpcase<EmailData>, headers: Headers, mut conn: DbConn)
email_data.reset_token(); email_data.reset_token();
twofactor.atype = TwoFactorType::Email as i32; twofactor.atype = TwoFactorType::Email as i32;
twofactor.data = email_data.to_json(); twofactor.data = email_data.to_json();
twofactor.save(&mut conn).await?; twofactor.save(&conn).await?;
_generate_recover_code(&mut user, &mut conn).await; _generate_recover_code(&mut user, &conn).await;
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await; log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
Ok(Json(json!({ Ok(Json(json!({
"Email": email_data.email, "Email": email_data.email,
@ -190,7 +190,7 @@ async fn email(data: JsonUpcase<EmailData>, headers: Headers, mut conn: DbConn)
} }
/// Validate the email code when used as TwoFactor token mechanism /// Validate the email code when used as TwoFactor token mechanism
pub async fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &mut DbConn) -> EmptyResult { pub async fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &DbConn) -> EmptyResult {
let mut email_data = EmailTokenData::from_json(data)?; let mut email_data = EmailTokenData::from_json(data)?;
let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Email as i32, conn) let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Email as i32, conn)
.await .await

Datei anzeigen

@ -38,8 +38,8 @@ pub fn routes() -> Vec<Route> {
} }
#[get("/two-factor")] #[get("/two-factor")]
async fn get_twofactor(headers: Headers, mut conn: DbConn) -> Json<Value> { async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> {
let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &mut conn).await; let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn).await;
let twofactors_json: Vec<Value> = twofactors.iter().map(TwoFactor::to_json_provider).collect(); let twofactors_json: Vec<Value> = twofactors.iter().map(TwoFactor::to_json_provider).collect();
Json(json!({ Json(json!({
@ -73,13 +73,13 @@ struct RecoverTwoFactor {
} }
#[post("/two-factor/recover", data = "<data>")] #[post("/two-factor/recover", data = "<data>")]
async fn recover(data: JsonUpcase<RecoverTwoFactor>, client_headers: ClientHeaders, mut conn: DbConn) -> JsonResult { async fn recover(data: JsonUpcase<RecoverTwoFactor>, client_headers: ClientHeaders, conn: DbConn) -> JsonResult {
let data: RecoverTwoFactor = data.into_inner().data; let data: RecoverTwoFactor = data.into_inner().data;
use crate::db::models::User; use crate::db::models::User;
// Get the user // Get the user
let mut user = match User::find_by_mail(&data.Email, &mut conn).await { let mut user = match User::find_by_mail(&data.Email, &conn).await {
Some(user) => user, Some(user) => user,
None => err!("Username or password is incorrect. Try again."), None => err!("Username or password is incorrect. Try again."),
}; };
@ -95,24 +95,24 @@ async fn recover(data: JsonUpcase<RecoverTwoFactor>, client_headers: ClientHeade
} }
// Remove all twofactors from the user // Remove all twofactors from the user
TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; TwoFactor::delete_all_by_user(&user.uuid, &conn).await?;
log_user_event( log_user_event(
EventType::UserRecovered2fa as i32, EventType::UserRecovered2fa as i32,
&user.uuid, &user.uuid,
client_headers.device_type, client_headers.device_type,
&client_headers.ip.ip, &client_headers.ip.ip,
&mut conn, &conn,
) )
.await; .await;
// Remove the recovery code, not needed without twofactors // Remove the recovery code, not needed without twofactors
user.totp_recover = None; user.totp_recover = None;
user.save(&mut conn).await?; user.save(&conn).await?;
Ok(Json(Value::Object(serde_json::Map::new()))) Ok(Json(Value::Object(serde_json::Map::new())))
} }
async fn _generate_recover_code(user: &mut User, conn: &mut DbConn) { async fn _generate_recover_code(user: &mut User, conn: &DbConn) {
if user.totp_recover.is_none() { if user.totp_recover.is_none() {
let totp_recover = crypto::encode_random_bytes::<20>(BASE32); let totp_recover = crypto::encode_random_bytes::<20>(BASE32);
user.totp_recover = Some(totp_recover); user.totp_recover = Some(totp_recover);
@ -128,7 +128,7 @@ struct DisableTwoFactorData {
} }
#[post("/two-factor/disable", data = "<data>")] #[post("/two-factor/disable", data = "<data>")]
async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: DisableTwoFactorData = data.into_inner().data; let data: DisableTwoFactorData = data.into_inner().data;
let password_hash = data.MasterPasswordHash; let password_hash = data.MasterPasswordHash;
let user = headers.user; let user = headers.user;
@ -139,26 +139,26 @@ async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Head
let type_ = data.Type.into_i32()?; let type_ = data.Type.into_i32()?;
if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await { if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await {
twofactor.delete(&mut conn).await?; twofactor.delete(&conn).await?;
log_user_event(EventType::UserDisabled2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn) log_user_event(EventType::UserDisabled2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn)
.await; .await;
} }
let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &mut conn).await.is_empty(); let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &conn).await.is_empty();
if twofactor_disabled { if twofactor_disabled {
for user_org in for user_org in
UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, &mut conn) UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, &conn)
.await .await
.into_iter() .into_iter()
{ {
if user_org.atype < UserOrgType::Admin { if user_org.atype < UserOrgType::Admin {
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
let org = Organization::find_by_uuid(&user_org.org_uuid, &mut conn).await.unwrap(); let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).await.unwrap();
mail::send_2fa_removed_from_org(&user.email, &org.name).await?; mail::send_2fa_removed_from_org(&user.email, &org.name).await?;
} }
user_org.delete(&mut conn).await?; user_org.delete(&conn).await?;
} }
} }
} }
@ -182,7 +182,7 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
return; return;
} }
let mut conn = match pool.get().await { let conn = match pool.get().await {
Ok(conn) => conn, Ok(conn) => conn,
_ => { _ => {
error!("Failed to get DB connection in send_incomplete_2fa_notifications()"); error!("Failed to get DB connection in send_incomplete_2fa_notifications()");
@ -193,9 +193,9 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
let time_limit = Duration::minutes(CONFIG.incomplete_2fa_time_limit()); let time_limit = Duration::minutes(CONFIG.incomplete_2fa_time_limit());
let time_before = now - time_limit; let time_before = now - time_limit;
let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &mut conn).await; let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &conn).await;
for login in incomplete_logins { for login in incomplete_logins {
let user = User::find_by_uuid(&login.user_uuid, &mut conn).await.expect("User not found"); let user = User::find_by_uuid(&login.user_uuid, &conn).await.expect("User not found");
info!( info!(
"User {} did not complete a 2FA login within the configured time limit. IP: {}", "User {} did not complete a 2FA login within the configured time limit. IP: {}",
user.email, login.ip_address user.email, login.ip_address
@ -203,7 +203,7 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
mail::send_incomplete_2fa_login(&user.email, &login.ip_address, &login.login_time, &login.device_name) mail::send_incomplete_2fa_login(&user.email, &login.ip_address, &login.login_time, &login.device_name)
.await .await
.expect("Error sending incomplete 2FA email"); .expect("Error sending incomplete 2FA email");
login.delete(&mut conn).await.expect("Error deleting incomplete 2FA record"); login.delete(&conn).await.expect("Error deleting incomplete 2FA record");
} }
} }

Datei anzeigen

@ -103,7 +103,7 @@ impl WebauthnRegistration {
} }
#[post("/two-factor/get-webauthn", data = "<data>")] #[post("/two-factor/get-webauthn", data = "<data>")]
async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
if !CONFIG.domain_set() { if !CONFIG.domain_set() {
err!("`DOMAIN` environment variable is not set. Webauthn disabled") err!("`DOMAIN` environment variable is not set. Webauthn disabled")
} }
@ -112,7 +112,7 @@ async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, mut conn
err!("Invalid password"); err!("Invalid password");
} }
let (enabled, registrations) = get_webauthn_registrations(&headers.user.uuid, &mut conn).await?; let (enabled, registrations) = get_webauthn_registrations(&headers.user.uuid, &conn).await?;
let registrations_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect(); let registrations_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect();
Ok(Json(json!({ Ok(Json(json!({
@ -123,12 +123,12 @@ async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, mut conn
} }
#[post("/two-factor/get-webauthn-challenge", data = "<data>")] #[post("/two-factor/get-webauthn-challenge", data = "<data>")]
async fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
if !headers.user.check_valid_password(&data.data.MasterPasswordHash) { if !headers.user.check_valid_password(&data.data.MasterPasswordHash) {
err!("Invalid password"); err!("Invalid password");
} }
let registrations = get_webauthn_registrations(&headers.user.uuid, &mut conn) let registrations = get_webauthn_registrations(&headers.user.uuid, &conn)
.await? .await?
.1 .1
.into_iter() .into_iter()
@ -145,7 +145,7 @@ async fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: He
)?; )?;
let type_ = TwoFactorType::WebauthnRegisterChallenge; let type_ = TwoFactorType::WebauthnRegisterChallenge;
TwoFactor::new(headers.user.uuid, type_, serde_json::to_string(&state)?).save(&mut conn).await?; TwoFactor::new(headers.user.uuid, type_, serde_json::to_string(&state)?).save(&conn).await?;
let mut challenge_value = serde_json::to_value(challenge.public_key)?; let mut challenge_value = serde_json::to_value(challenge.public_key)?;
challenge_value["status"] = "ok".into(); challenge_value["status"] = "ok".into();
@ -242,7 +242,7 @@ impl From<PublicKeyCredentialCopy> for PublicKeyCredential {
} }
#[post("/two-factor/webauthn", data = "<data>")] #[post("/two-factor/webauthn", data = "<data>")]
async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: EnableWebauthnData = data.into_inner().data; let data: EnableWebauthnData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -252,10 +252,10 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header
// Retrieve and delete the saved challenge state // Retrieve and delete the saved challenge state
let type_ = TwoFactorType::WebauthnRegisterChallenge as i32; let type_ = TwoFactorType::WebauthnRegisterChallenge as i32;
let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await { let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await {
Some(tf) => { Some(tf) => {
let state: RegistrationState = serde_json::from_str(&tf.data)?; let state: RegistrationState = serde_json::from_str(&tf.data)?;
tf.delete(&mut conn).await?; tf.delete(&conn).await?;
state state
} }
None => err!("Can't recover challenge"), None => err!("Can't recover challenge"),
@ -265,7 +265,7 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header
let (credential, _data) = let (credential, _data) =
WebauthnConfig::load().register_credential(&data.DeviceResponse.into(), &state, |_| Ok(false))?; WebauthnConfig::load().register_credential(&data.DeviceResponse.into(), &state, |_| Ok(false))?;
let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &mut conn).await?.1; let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &conn).await?.1;
// TODO: Check for repeated ID's // TODO: Check for repeated ID's
registrations.push(WebauthnRegistration { registrations.push(WebauthnRegistration {
id: data.Id.into_i32()?, id: data.Id.into_i32()?,
@ -277,11 +277,11 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header
// Save the registrations and return them // Save the registrations and return them
TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(&registrations)?) TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(&registrations)?)
.save(&mut conn) .save(&conn)
.await?; .await?;
_generate_recover_code(&mut user, &mut conn).await; _generate_recover_code(&mut user, &conn).await;
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await; log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
let keys_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect(); let keys_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect();
Ok(Json(json!({ Ok(Json(json!({
@ -304,17 +304,17 @@ struct DeleteU2FData {
} }
#[delete("/two-factor/webauthn", data = "<data>")] #[delete("/two-factor/webauthn", data = "<data>")]
async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult {
let id = data.data.Id.into_i32()?; let id = data.data.Id.into_i32()?;
if !headers.user.check_valid_password(&data.data.MasterPasswordHash) { if !headers.user.check_valid_password(&data.data.MasterPasswordHash) {
err!("Invalid password"); err!("Invalid password");
} }
let mut tf = let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &conn).await
match TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &mut conn).await { {
Some(tf) => tf, Some(tf) => tf,
None => err!("Webauthn data not found!"), None => err!("Webauthn data not found!"),
}; };
let mut data: Vec<WebauthnRegistration> = serde_json::from_str(&tf.data)?; let mut data: Vec<WebauthnRegistration> = serde_json::from_str(&tf.data)?;
@ -325,12 +325,11 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, mut
let removed_item = data.remove(item_pos); let removed_item = data.remove(item_pos);
tf.data = serde_json::to_string(&data)?; tf.data = serde_json::to_string(&data)?;
tf.save(&mut conn).await?; tf.save(&conn).await?;
drop(tf); drop(tf);
// If entry is migrated from u2f, delete the u2f entry as well // If entry is migrated from u2f, delete the u2f entry as well
if let Some(mut u2f) = if let Some(mut u2f) = TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &conn).await
TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &mut conn).await
{ {
let mut data: Vec<U2FRegistration> = match serde_json::from_str(&u2f.data) { let mut data: Vec<U2FRegistration> = match serde_json::from_str(&u2f.data) {
Ok(d) => d, Ok(d) => d,
@ -341,7 +340,7 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, mut
let new_data_str = serde_json::to_string(&data)?; let new_data_str = serde_json::to_string(&data)?;
u2f.data = new_data_str; u2f.data = new_data_str;
u2f.save(&mut conn).await?; u2f.save(&conn).await?;
} }
let keys_json: Vec<Value> = data.iter().map(WebauthnRegistration::to_json).collect(); let keys_json: Vec<Value> = data.iter().map(WebauthnRegistration::to_json).collect();
@ -355,7 +354,7 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, mut
pub async fn get_webauthn_registrations( pub async fn get_webauthn_registrations(
user_uuid: &str, user_uuid: &str,
conn: &mut DbConn, conn: &DbConn,
) -> Result<(bool, Vec<WebauthnRegistration>), Error> { ) -> Result<(bool, Vec<WebauthnRegistration>), Error> {
let type_ = TwoFactorType::Webauthn as i32; let type_ = TwoFactorType::Webauthn as i32;
match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await {
@ -364,7 +363,7 @@ pub async fn get_webauthn_registrations(
} }
} }
pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> JsonResult { pub async fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult {
// Load saved credentials // Load saved credentials
let creds: Vec<Credential> = let creds: Vec<Credential> =
get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect(); get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect();
@ -386,7 +385,7 @@ pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> Json
Ok(Json(serde_json::to_value(response.public_key)?)) Ok(Json(serde_json::to_value(response.public_key)?))
} }
pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut DbConn) -> EmptyResult { pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult {
let type_ = TwoFactorType::WebauthnLoginChallenge as i32; let type_ = TwoFactorType::WebauthnLoginChallenge as i32;
let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await {
Some(tf) => { Some(tf) => {

Datei anzeigen

@ -83,7 +83,7 @@ async fn verify_yubikey_otp(otp: String) -> EmptyResult {
} }
#[post("/two-factor/get-yubikey", data = "<data>")] #[post("/two-factor/get-yubikey", data = "<data>")]
async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
// Make sure the credentials are set // Make sure the credentials are set
get_yubico_credentials()?; get_yubico_credentials()?;
@ -97,7 +97,7 @@ async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, mut
let user_uuid = &user.uuid; let user_uuid = &user.uuid;
let yubikey_type = TwoFactorType::YubiKey as i32; let yubikey_type = TwoFactorType::YubiKey as i32;
let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &mut conn).await; let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn).await;
if let Some(r) = r { if let Some(r) = r {
let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?; let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?;
@ -118,7 +118,7 @@ async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, mut
} }
#[post("/two-factor/yubikey", data = "<data>")] #[post("/two-factor/yubikey", data = "<data>")]
async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: EnableYubikeyData = data.into_inner().data; let data: EnableYubikeyData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -128,7 +128,7 @@ async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers,
// Check if we already have some data // Check if we already have some data
let mut yubikey_data = let mut yubikey_data =
match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &mut conn).await { match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &conn).await {
Some(data) => data, Some(data) => data,
None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()), None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()),
}; };
@ -160,11 +160,11 @@ async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers,
}; };
yubikey_data.data = serde_json::to_string(&yubikey_metadata).unwrap(); yubikey_data.data = serde_json::to_string(&yubikey_metadata).unwrap();
yubikey_data.save(&mut conn).await?; yubikey_data.save(&conn).await?;
_generate_recover_code(&mut user, &mut conn).await; _generate_recover_code(&mut user, &conn).await;
log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &mut conn).await; log_user_event(EventType::UserUpdated2fa as i32, &user.uuid, headers.device.atype, &headers.ip.ip, &conn).await;
let mut result = jsonify_yubikeys(yubikey_metadata.Keys); let mut result = jsonify_yubikeys(yubikey_metadata.Keys);

Datei anzeigen

@ -25,7 +25,7 @@ pub fn routes() -> Vec<Route> {
} }
#[post("/connect/token", data = "<data>")] #[post("/connect/token", data = "<data>")]
async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn: DbConn) -> JsonResult { async fn login(data: Form<ConnectData>, client_header: ClientHeaders, conn: DbConn) -> JsonResult {
let data: ConnectData = data.into_inner(); let data: ConnectData = data.into_inner();
let mut user_uuid: Option<String> = None; let mut user_uuid: Option<String> = None;
@ -33,7 +33,7 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
let login_result = match data.grant_type.as_ref() { let login_result = match data.grant_type.as_ref() {
"refresh_token" => { "refresh_token" => {
_check_is_some(&data.refresh_token, "refresh_token cannot be blank")?; _check_is_some(&data.refresh_token, "refresh_token cannot be blank")?;
_refresh_login(data, &mut conn).await _refresh_login(data, &conn).await
} }
"password" => { "password" => {
_check_is_some(&data.client_id, "client_id cannot be blank")?; _check_is_some(&data.client_id, "client_id cannot be blank")?;
@ -45,7 +45,7 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
_check_is_some(&data.device_name, "device_name cannot be blank")?; _check_is_some(&data.device_name, "device_name cannot be blank")?;
_check_is_some(&data.device_type, "device_type cannot be blank")?; _check_is_some(&data.device_type, "device_type cannot be blank")?;
_password_login(data, &mut user_uuid, &mut conn, &client_header.ip).await _password_login(data, &mut user_uuid, &conn, &client_header.ip).await
} }
"client_credentials" => { "client_credentials" => {
_check_is_some(&data.client_id, "client_id cannot be blank")?; _check_is_some(&data.client_id, "client_id cannot be blank")?;
@ -56,7 +56,7 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
_check_is_some(&data.device_name, "device_name cannot be blank")?; _check_is_some(&data.device_name, "device_name cannot be blank")?;
_check_is_some(&data.device_type, "device_type cannot be blank")?; _check_is_some(&data.device_type, "device_type cannot be blank")?;
_api_key_login(data, &mut user_uuid, &mut conn, &client_header.ip).await _api_key_login(data, &mut user_uuid, &conn, &client_header.ip).await
} }
t => err!("Invalid type", t), t => err!("Invalid type", t),
}; };
@ -69,20 +69,14 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
&user_uuid, &user_uuid,
client_header.device_type, client_header.device_type,
&client_header.ip.ip, &client_header.ip.ip,
&mut conn, &conn,
) )
.await; .await;
} }
Err(e) => { Err(e) => {
if let Some(ev) = e.get_event() { if let Some(ev) = e.get_event() {
log_user_event( log_user_event(ev.event as i32, &user_uuid, client_header.device_type, &client_header.ip.ip, &conn)
ev.event as i32, .await
&user_uuid,
client_header.device_type,
&client_header.ip.ip,
&mut conn,
)
.await
} }
} }
} }
@ -91,7 +85,7 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
login_result login_result
} }
async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult { async fn _refresh_login(data: ConnectData, conn: &DbConn) -> JsonResult {
// Extract token // Extract token
let token = data.refresh_token.unwrap(); let token = data.refresh_token.unwrap();
@ -130,7 +124,7 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
async fn _password_login( async fn _password_login(
data: ConnectData, data: ConnectData,
user_uuid: &mut Option<String>, user_uuid: &mut Option<String>,
conn: &mut DbConn, conn: &DbConn,
ip: &ClientIp, ip: &ClientIp,
) -> JsonResult { ) -> JsonResult {
// Validate scope // Validate scope
@ -294,12 +288,7 @@ async fn _password_login(
Ok(Json(result)) Ok(Json(result))
} }
async fn _api_key_login( async fn _api_key_login(data: ConnectData, user_uuid: &mut Option<String>, conn: &DbConn, ip: &ClientIp) -> JsonResult {
data: ConnectData,
user_uuid: &mut Option<String>,
conn: &mut DbConn,
ip: &ClientIp,
) -> JsonResult {
// Ratelimit the login // Ratelimit the login
crate::ratelimit::check_limit_login(&ip.ip)?; crate::ratelimit::check_limit_login(&ip.ip)?;
@ -314,7 +303,7 @@ async fn _api_key_login(
async fn _user_api_key_login( async fn _user_api_key_login(
data: ConnectData, data: ConnectData,
user_uuid: &mut Option<String>, user_uuid: &mut Option<String>,
conn: &mut DbConn, conn: &DbConn,
ip: &ClientIp, ip: &ClientIp,
) -> JsonResult { ) -> JsonResult {
// Get the user via the client_id // Get the user via the client_id
@ -401,7 +390,7 @@ async fn _user_api_key_login(
Ok(Json(result)) Ok(Json(result))
} }
async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &ClientIp) -> JsonResult { async fn _organization_api_key_login(data: ConnectData, conn: &DbConn, ip: &ClientIp) -> JsonResult {
// Get the org via the client_id // Get the org via the client_id
let client_id = data.client_id.as_ref().unwrap(); let client_id = data.client_id.as_ref().unwrap();
let org_uuid = match client_id.strip_prefix("organization.") { let org_uuid = match client_id.strip_prefix("organization.") {
@ -432,7 +421,7 @@ async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &
} }
/// Retrieves an existing device or creates a new device from ConnectData and the User /// Retrieves an existing device or creates a new device from ConnectData and the User
async fn get_device(data: &ConnectData, conn: &mut DbConn, user: &User) -> (Device, bool) { async fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) {
// On iOS, device_type sends "iOS", on others it sends a number // On iOS, device_type sends "iOS", on others it sends a number
// When unknown or unable to parse, return 14, which is 'Unknown Browser' // When unknown or unable to parse, return 14, which is 'Unknown Browser'
let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(14); let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(14);
@ -457,7 +446,7 @@ async fn twofactor_auth(
data: &ConnectData, data: &ConnectData,
device: &mut Device, device: &mut Device,
ip: &ClientIp, ip: &ClientIp,
conn: &mut DbConn, conn: &DbConn,
) -> ApiResult<Option<String>> { ) -> ApiResult<Option<String>> {
let twofactors = TwoFactor::find_by_user(user_uuid, conn).await; let twofactors = TwoFactor::find_by_user(user_uuid, conn).await;
@ -534,7 +523,7 @@ fn _selected_data(tf: Option<TwoFactor>) -> ApiResult<String> {
tf.map(|t| t.data).map_res("Two factor doesn't exist") tf.map(|t| t.data).map_res("Two factor doesn't exist")
} }
async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &mut DbConn) -> ApiResult<Value> { async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> ApiResult<Value> {
use crate::api::core::two_factor; use crate::api::core::two_factor;
let mut result = json!({ let mut result = json!({

Datei anzeigen

@ -371,7 +371,7 @@ impl WebSocketUsers {
ut: UpdateType, ut: UpdateType,
folder: &Folder, folder: &Folder,
acting_device_uuid: &String, acting_device_uuid: &String,
conn: &mut DbConn, conn: &DbConn,
) { ) {
let data = create_update( let data = create_update(
vec![ vec![
@ -397,7 +397,7 @@ impl WebSocketUsers {
user_uuids: &[String], user_uuids: &[String],
acting_device_uuid: &String, acting_device_uuid: &String,
collection_uuids: Option<Vec<String>>, collection_uuids: Option<Vec<String>>,
conn: &mut DbConn, conn: &DbConn,
) { ) {
let org_uuid = convert_option(cipher.organization_uuid.clone()); let org_uuid = convert_option(cipher.organization_uuid.clone());
// Depending if there are collections provided or not, we need to have different values for the following variables. // Depending if there are collections provided or not, we need to have different values for the following variables.
@ -439,7 +439,7 @@ impl WebSocketUsers {
send: &DbSend, send: &DbSend,
user_uuids: &[String], user_uuids: &[String],
acting_device_uuid: &String, acting_device_uuid: &String,
conn: &mut DbConn, conn: &DbConn,
) { ) {
let user_uuid = convert_option(send.user_uuid.clone()); let user_uuid = convert_option(send.user_uuid.clone());
@ -466,7 +466,7 @@ impl WebSocketUsers {
user_uuid: &String, user_uuid: &String,
auth_request_uuid: &String, auth_request_uuid: &String,
acting_device_uuid: &String, acting_device_uuid: &String,
conn: &mut DbConn, conn: &DbConn,
) { ) {
let data = create_update( let data = create_update(
vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_uuid.clone().into())], vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_uuid.clone().into())],
@ -485,7 +485,7 @@ impl WebSocketUsers {
user_uuid: &String, user_uuid: &String,
auth_response_uuid: &str, auth_response_uuid: &str,
approving_device_uuid: String, approving_device_uuid: String,
conn: &mut DbConn, conn: &DbConn,
) { ) {
let data = create_update( let data = create_update(
vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())], vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())],

Datei anzeigen

@ -4,7 +4,10 @@ use tokio::sync::RwLock;
use crate::{ use crate::{
api::{ApiResult, EmptyResult, UpdateType}, api::{ApiResult, EmptyResult, UpdateType},
db::models::{Cipher, Device, Folder, Send, User}, db::{
models::{Cipher, Device, Folder, Send, User},
DbConn,
},
util::get_reqwest_client, util::get_reqwest_client,
CONFIG, CONFIG,
}; };
@ -121,12 +124,7 @@ pub async fn unregister_push_device(uuid: String) -> EmptyResult {
Ok(()) Ok(())
} }
pub async fn push_cipher_update( pub async fn push_cipher_update(ut: UpdateType, cipher: &Cipher, acting_device_uuid: &String, conn: &DbConn) {
ut: UpdateType,
cipher: &Cipher,
acting_device_uuid: &String,
conn: &mut crate::db::DbConn,
) {
// We shouldn't send a push notification on cipher update if the cipher belongs to an organization, this isn't implemented in the upstream server too. // We shouldn't send a push notification on cipher update if the cipher belongs to an organization, this isn't implemented in the upstream server too.
if cipher.organization_uuid.is_some() { if cipher.organization_uuid.is_some() {
return; return;
@ -187,12 +185,7 @@ pub fn push_user_update(ut: UpdateType, user: &User) {
}))); })));
} }
pub async fn push_folder_update( pub async fn push_folder_update(ut: UpdateType, folder: &Folder, acting_device_uuid: &String, conn: &DbConn) {
ut: UpdateType,
folder: &Folder,
acting_device_uuid: &String,
conn: &mut crate::db::DbConn,
) {
if Device::check_user_has_push_device(&folder.user_uuid, conn).await { if Device::check_user_has_push_device(&folder.user_uuid, conn).await {
tokio::task::spawn(send_to_push_relay(json!({ tokio::task::spawn(send_to_push_relay(json!({
"userId": folder.user_uuid, "userId": folder.user_uuid,
@ -209,7 +202,7 @@ pub async fn push_folder_update(
} }
} }
pub async fn push_send_update(ut: UpdateType, send: &Send, acting_device_uuid: &String, conn: &mut crate::db::DbConn) { pub async fn push_send_update(ut: UpdateType, send: &Send, acting_device_uuid: &String, conn: &DbConn) {
if let Some(s) = &send.user_uuid { if let Some(s) = &send.user_uuid {
if Device::check_user_has_push_device(s, conn).await { if Device::check_user_has_push_device(s, conn).await {
tokio::task::spawn(send_to_push_relay(json!({ tokio::task::spawn(send_to_push_relay(json!({
@ -256,7 +249,7 @@ async fn send_to_push_relay(notification_data: Value) {
}; };
} }
pub async fn push_auth_request(user_uuid: String, auth_request_uuid: String, conn: &mut crate::db::DbConn) { pub async fn push_auth_request(user_uuid: String, auth_request_uuid: String, conn: &DbConn) {
if Device::check_user_has_push_device(user_uuid.as_str(), conn).await { if Device::check_user_has_push_device(user_uuid.as_str(), conn).await {
tokio::task::spawn(send_to_push_relay(json!({ tokio::task::spawn(send_to_push_relay(json!({
"userId": user_uuid, "userId": user_uuid,
@ -276,7 +269,7 @@ pub async fn push_auth_response(
user_uuid: String, user_uuid: String,
auth_request_uuid: String, auth_request_uuid: String,
approving_device_uuid: String, approving_device_uuid: String,
conn: &mut crate::db::DbConn, conn: &DbConn,
) { ) {
if Device::check_user_has_push_device(user_uuid.as_str(), conn).await { if Device::check_user_has_push_device(user_uuid.as_str(), conn).await {
tokio::task::spawn(send_to_push_relay(json!({ tokio::task::spawn(send_to_push_relay(json!({

Datei anzeigen

@ -442,17 +442,17 @@ impl<'r> FromRequest<'r> for Headers {
let device_uuid = claims.device; let device_uuid = claims.device;
let user_uuid = claims.sub; let user_uuid = claims.sub;
let mut conn = match DbConn::from_request(request).await { let conn = match DbConn::from_request(request).await {
Outcome::Success(conn) => conn, Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB"), _ => err_handler!("Error getting DB"),
}; };
let device = match Device::find_by_uuid_and_user(&device_uuid, &user_uuid, &mut conn).await { let device = match Device::find_by_uuid_and_user(&device_uuid, &user_uuid, &conn).await {
Some(device) => device, Some(device) => device,
None => err_handler!("Invalid device id"), None => err_handler!("Invalid device id"),
}; };
let user = match User::find_by_uuid(&user_uuid, &mut conn).await { let user = match User::find_by_uuid(&user_uuid, &conn).await {
Some(user) => user, Some(user) => user,
None => err_handler!("Device has no user associated"), None => err_handler!("Device has no user associated"),
}; };
@ -474,7 +474,7 @@ impl<'r> FromRequest<'r> for Headers {
// This prevents checking this stamp exception for new requests. // This prevents checking this stamp exception for new requests.
let mut user = user; let mut user = user;
user.reset_stamp_exception(); user.reset_stamp_exception();
if let Err(e) = user.save(&mut conn).await { if let Err(e) = user.save(&conn).await {
error!("Error updating user: {:#?}", e); error!("Error updating user: {:#?}", e);
} }
err_handler!("Stamp exception is expired") err_handler!("Stamp exception is expired")
@ -536,13 +536,13 @@ impl<'r> FromRequest<'r> for OrgHeaders {
match url_org_id { match url_org_id {
Some(org_id) => { Some(org_id) => {
let mut conn = match DbConn::from_request(request).await { let conn = match DbConn::from_request(request).await {
Outcome::Success(conn) => conn, Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB"), _ => err_handler!("Error getting DB"),
}; };
let user = headers.user; let user = headers.user;
let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, org_id, &mut conn).await { let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, org_id, &conn).await {
Some(user) => { Some(user) => {
if user.status == UserOrgStatus::Confirmed as i32 { if user.status == UserOrgStatus::Confirmed as i32 {
user user
@ -656,12 +656,12 @@ impl<'r> FromRequest<'r> for ManagerHeaders {
if headers.org_user_type >= UserOrgType::Manager { if headers.org_user_type >= UserOrgType::Manager {
match get_col_id(request) { match get_col_id(request) {
Some(col_id) => { Some(col_id) => {
let mut conn = match DbConn::from_request(request).await { let conn = match DbConn::from_request(request).await {
Outcome::Success(conn) => conn, Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB"), _ => err_handler!("Error getting DB"),
}; };
if !can_access_collection(&headers.org_user, &col_id, &mut conn).await { if !can_access_collection(&headers.org_user, &col_id, &conn).await {
err_handler!("The current user isn't a manager for this collection") err_handler!("The current user isn't a manager for this collection")
} }
} }
@ -734,7 +734,7 @@ impl From<ManagerHeadersLoose> for Headers {
} }
} }
} }
async fn can_access_collection(org_user: &UserOrganization, col_id: &str, conn: &mut DbConn) -> bool { async fn can_access_collection(org_user: &UserOrganization, col_id: &str, conn: &DbConn) -> bool {
org_user.has_full_access() org_user.has_full_access()
|| Collection::has_access_by_collection_and_user_uuid(col_id, &org_user.user_uuid, conn).await || Collection::has_access_by_collection_and_user_uuid(col_id, &org_user.user_uuid, conn).await
} }
@ -743,7 +743,7 @@ impl ManagerHeaders {
pub async fn from_loose( pub async fn from_loose(
h: ManagerHeadersLoose, h: ManagerHeadersLoose,
collections: &Vec<String>, collections: &Vec<String>,
conn: &mut DbConn, conn: &DbConn,
) -> Result<ManagerHeaders, Error> { ) -> Result<ManagerHeaders, Error> {
for col_id in collections { for col_id in collections {
if uuid::Uuid::parse_str(col_id).is_err() { if uuid::Uuid::parse_str(col_id).is_err() {

Datei anzeigen

@ -7,7 +7,6 @@ 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},
}; };
@ -689,12 +688,18 @@ make_config! {
fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
// Validate connection URL is valid and DB feature is enabled // Validate connection URL is valid and DB feature is enabled
let url = &cfg.database_url; #[cfg(sqlite)]
if DbConnType::from_url(url)? == DbConnType::sqlite && url.contains('/') { {
let path = std::path::Path::new(&url); let url = &cfg.database_url;
if let Some(parent) = path.parent() { if crate::db::DbConnType::from_url(url)? == crate::db::DbConnType::Sqlite && url.contains('/') {
if !parent.is_dir() { let path = std::path::Path::new(&url);
err!(format!("SQLite database directory `{}` does not exist or is not a directory", parent.display())); if let Some(parent) = path.parent() {
if !parent.is_dir() {
err!(format!(
"SQLite database directory `{}` does not exist or is not a directory",
parent.display()
));
}
} }
} }
} }

Datei anzeigen

@ -22,20 +22,7 @@ use crate::{
CONFIG, CONFIG,
}; };
#[cfg(sqlite)]
#[path = "schemas/sqlite/schema.rs"]
pub mod __sqlite_schema;
#[cfg(mysql)]
#[path = "schemas/mysql/schema.rs"]
pub mod __mysql_schema;
#[cfg(postgresql)]
#[path = "schemas/postgresql/schema.rs"]
pub mod __postgresql_schema;
// These changes are based on Rocket 0.5-rc wrapper of Diesel: https://github.com/SergioBenitez/Rocket/blob/v0.5-rc/contrib/sync_db_pools // These changes are based on Rocket 0.5-rc wrapper of Diesel: https://github.com/SergioBenitez/Rocket/blob/v0.5-rc/contrib/sync_db_pools
// A wrapper around spawn_blocking that propagates panics to the calling code. // A wrapper around spawn_blocking that propagates panics to the calling code.
pub async fn run_blocking<F, R>(job: F) -> R pub async fn run_blocking<F, R>(job: F) -> R
where where
@ -51,171 +38,166 @@ where
} }
} }
// This is used to generate the main DbConn and DbPool enums, which contain one variant for each database supported #[derive(diesel::MultiConnection)]
macro_rules! generate_connections { pub enum DbConnInner {
( $( $name:ident: $ty:ty ),+ ) => { #[cfg(sqlite)]
#[allow(non_camel_case_types, dead_code)] Sqlite(diesel::sqlite::SqliteConnection),
#[derive(Eq, PartialEq)] #[cfg(mysql)]
pub enum DbConnType { $( $name, )+ } Mysql(diesel::mysql::MysqlConnection),
#[cfg(postgresql)]
Postgresql(diesel::pg::PgConnection),
}
pub struct DbConn { #[derive(Eq, PartialEq)]
conn: Arc<Mutex<Option<DbConnInner>>>, pub enum DbConnType {
permit: Option<OwnedSemaphorePermit>, #[cfg(sqlite)]
Sqlite,
#[cfg(mysql)]
Mysql,
#[cfg(postgresql)]
Postgresql,
}
pub struct DbConn {
conn: Arc<Mutex<Option<PooledConnection<ConnectionManager<DbConnInner>>>>>,
permit: Option<OwnedSemaphorePermit>,
}
#[derive(Debug)]
pub struct DbConnOptions {
pub init_stmts: String,
}
impl CustomizeConnection<DbConnInner, diesel::r2d2::Error> for DbConnOptions {
fn on_acquire(&self, conn: &mut DbConnInner) -> Result<(), diesel::r2d2::Error> {
if !self.init_stmts.is_empty() {
conn.batch_execute(&self.init_stmts).map_err(diesel::r2d2::Error::QueryError)?;
} }
Ok(())
}
}
#[allow(non_camel_case_types)] #[derive(Clone)]
pub enum DbConnInner { $( #[cfg($name)] $name(PooledConnection<ConnectionManager< $ty >>), )+ } pub struct DbPool {
pool: Option<Pool<ConnectionManager<DbConnInner>>>,
semaphore: Arc<Semaphore>,
}
#[derive(Debug)] impl Drop for DbConn {
pub struct DbConnOptions { fn drop(&mut self) {
pub init_stmts: String, let conn = Arc::clone(&self.conn);
let permit = self.permit.take();
tokio::task::spawn_blocking(move || {
let mut conn = tokio::runtime::Handle::current().block_on(conn.lock_owned());
if let Some(conn) = conn.take() {
drop(conn);
}
drop(permit);
});
}
}
impl Drop for DbPool {
fn drop(&mut self) {
let pool = self.pool.take();
// Only use spawn_blocking if the Tokio runtime is still available
if let Ok(handle) = tokio::runtime::Handle::try_current() {
handle.spawn_blocking(move || drop(pool));
} }
// Otherwise the pool will be dropped on the current thread
}
}
$( // Based on <https://stackoverflow.com/a/57717533>. impl DbPool {
#[cfg($name)] pub fn from_config() -> Result<Self, Error> {
impl CustomizeConnection<$ty, diesel::r2d2::Error> for DbConnOptions { let url = CONFIG.database_url();
fn on_acquire(&self, conn: &mut $ty) -> Result<(), diesel::r2d2::Error> { let conn_type = DbConnType::from_url(&url)?;
if !self.init_stmts.is_empty() { match conn_type {
conn.batch_execute(&self.init_stmts).map_err(diesel::r2d2::Error::QueryError)?; #[cfg(sqlite)]
DbConnType::Sqlite => {
#[cfg(feature = "sqlite")]
{
sqlite_migrations::run_migrations(&url)?;
} }
Ok(())
} }
})+ #[cfg(mysql)]
DbConnType::Mysql => {
#[derive(Clone)] #[cfg(feature = "mysql")]
pub struct DbPool { {
// This is an 'Option' so that we can drop the pool in a 'spawn_blocking'. mysql_migrations::run_migrations(&url)?;
pool: Option<DbPoolInner>, }
semaphore: Arc<Semaphore> }
} #[cfg(postgresql)]
DbConnType::Postgresql => {
#[allow(non_camel_case_types)] #[cfg(feature = "postgresql")]
#[derive(Clone)] {
pub enum DbPoolInner { $( #[cfg($name)] $name(Pool<ConnectionManager< $ty >>), )+ } postgresql_migrations::run_migrations(&url)?;
}
impl Drop for DbConn {
fn drop(&mut self) {
let conn = Arc::clone(&self.conn);
let permit = self.permit.take();
// Since connection can't be on the stack in an async fn during an
// await, we have to spawn a new blocking-safe thread...
tokio::task::spawn_blocking(move || {
// And then re-enter the runtime to wait on the async mutex, but in a blocking fashion.
let mut conn = tokio::runtime::Handle::current().block_on(conn.lock_owned());
if let Some(conn) = conn.take() {
drop(conn);
}
// Drop permit after the connection is dropped
drop(permit);
});
} }
} }
impl Drop for DbPool { let max_conns = CONFIG.database_max_conns();
fn drop(&mut self) { let manager = ConnectionManager::<DbConnInner>::new(&url);
let pool = self.pool.take(); let pool = Pool::builder()
tokio::task::spawn_blocking(move || drop(pool)); .max_size(max_conns)
.connection_timeout(Duration::from_secs(CONFIG.database_timeout()))
.connection_customizer(Box::new(DbConnOptions {
init_stmts: conn_type.get_init_stmts(),
}))
.build(manager)
.map_res("Failed to create pool")?;
Ok(DbPool {
pool: Some(pool),
semaphore: Arc::new(Semaphore::new(max_conns as usize)),
})
}
pub async fn get(&self) -> Result<DbConn, Error> {
let duration = Duration::from_secs(CONFIG.database_timeout());
let permit = match timeout(duration, Arc::clone(&self.semaphore).acquire_owned()).await {
Ok(p) => p.expect("Semaphore should be open"),
Err(_) => {
err!("Timeout waiting for database connection");
} }
} };
impl DbPool { let p = self.pool.as_ref().expect("DbPool.pool should always be Some()");
// For the given database URL, guess its type, run migrations, create pool, and return it let pool = p.clone();
pub fn from_config() -> Result<Self, Error> { let c =
let url = CONFIG.database_url(); run_blocking(move || pool.get_timeout(duration)).await.map_res("Error retrieving connection from pool")?;
let conn_type = DbConnType::from_url(&url)?; Ok(DbConn {
conn: Arc::new(Mutex::new(Some(c))),
match conn_type { $( permit: Some(permit),
DbConnType::$name => { })
#[cfg($name)] }
{
paste::paste!{ [< $name _migrations >]::run_migrations()?; }
let manager = ConnectionManager::new(&url);
let pool = Pool::builder()
.max_size(CONFIG.database_max_conns())
.connection_timeout(Duration::from_secs(CONFIG.database_timeout()))
.connection_customizer(Box::new(DbConnOptions{
init_stmts: conn_type.get_init_stmts()
}))
.build(manager)
.map_res("Failed to create pool")?;
Ok(DbPool {
pool: Some(DbPoolInner::$name(pool)),
semaphore: Arc::new(Semaphore::new(CONFIG.database_max_conns() as usize)),
})
}
#[cfg(not($name))]
unreachable!("Trying to use a DB backend when it's feature is disabled")
},
)+ }
}
// Get a connection from the pool
pub async fn get(&self) -> Result<DbConn, Error> {
let duration = Duration::from_secs(CONFIG.database_timeout());
let permit = match timeout(duration, Arc::clone(&self.semaphore).acquire_owned()).await {
Ok(p) => p.expect("Semaphore should be open"),
Err(_) => {
err!("Timeout waiting for database connection");
}
};
match self.pool.as_ref().expect("DbPool.pool should always be Some()") { $(
#[cfg($name)]
DbPoolInner::$name(p) => {
let pool = p.clone();
let c = run_blocking(move || pool.get_timeout(duration)).await.map_res("Error retrieving connection from pool")?;
Ok(DbConn {
conn: Arc::new(Mutex::new(Some(DbConnInner::$name(c)))),
permit: Some(permit)
})
},
)+ }
}
}
};
}
#[cfg(not(query_logger))]
generate_connections! {
sqlite: diesel::sqlite::SqliteConnection,
mysql: diesel::mysql::MysqlConnection,
postgresql: diesel::pg::PgConnection
}
#[cfg(query_logger)]
generate_connections! {
sqlite: diesel_logger::LoggingConnection<diesel::sqlite::SqliteConnection>,
mysql: diesel_logger::LoggingConnection<diesel::mysql::MysqlConnection>,
postgresql: diesel_logger::LoggingConnection<diesel::pg::PgConnection>
} }
impl DbConnType { impl DbConnType {
pub fn from_url(url: &str) -> Result<DbConnType, Error> { // pub enum Backend
pub fn from_url(url: &str) -> Result<Self, Error> {
// Mysql // Mysql
if url.starts_with("mysql:") { if url.len() > 6 && &url[..6] == "mysql:" {
#[cfg(mysql)] #[cfg(feature = "mysql")]
return Ok(DbConnType::mysql); return Ok(DbConnType::Mysql);
#[cfg(not(mysql))] #[cfg(not(feature = "mysql"))]
err!("`DATABASE_URL` is a MySQL URL, but the 'mysql' feature is not enabled") err!("`DATABASE_URL` is a MySQL URL, but the 'mysql' feature is not enabled")
// Postgres // Postgresql
} else if url.starts_with("postgresql:") || url.starts_with("postgres:") { } else if url.len() > 11 && (&url[..11] == "postgresql:" || &url[..9] == "postgres:") {
#[cfg(postgresql)] #[cfg(feature = "postgresql")]
return Ok(DbConnType::postgresql); return Ok(DbConnType::Postgresql);
#[cfg(not(postgresql))] #[cfg(not(feature = "postgresql"))]
err!("`DATABASE_URL` is a PostgreSQL URL, but the 'postgresql' feature is not enabled") err!("`DATABASE_URL` is a PostgreSQL URL, but the 'postgresql' feature is not enabled")
//Sqlite //Sqlite
} else { } else {
#[cfg(sqlite)] #[cfg(feature = "sqlite")]
return Ok(DbConnType::sqlite); return Ok(DbConnType::Sqlite);
#[cfg(not(sqlite))] #[cfg(not(feature = "sqlite"))]
err!("`DATABASE_URL` looks like a SQLite URL, but 'sqlite' feature is not enabled") err!("`DATABASE_URL` looks like a SQLite URL, but 'sqlite' feature is not enabled")
} }
} }
@ -231,148 +213,61 @@ impl DbConnType {
pub fn default_init_stmts(&self) -> String { pub fn default_init_stmts(&self) -> String {
match self { match self {
Self::sqlite => "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;".to_string(), #[cfg(sqlite)]
Self::mysql => String::new(), Self::Sqlite => "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;".to_string(),
Self::postgresql => String::new(), #[cfg(mysql)]
Self::Mysql => String::new(),
#[cfg(postgresql)]
Self::Postgresql => String::new(),
} }
} }
} }
// Shared base code for the db_run macro.
macro_rules! db_run_base {
( $conn:ident ) => {
#[allow(unused)]
use diesel::prelude::*;
#[allow(unused)]
use $crate::db::models::{self, *};
#[allow(unused)]
use $crate::db::schema::{self, *};
let conn = $conn.conn.clone();
let mut conn = conn.lock_owned().await;
let $conn = conn.as_mut().expect("internal invariant broken: self.conn is Some");
};
}
#[macro_export] #[macro_export]
macro_rules! db_run { macro_rules! db_run {
// Same for all dbs ( $conn:ident: $body:block ) => {{
( $conn:ident: $body:block ) => { db_run_base!($conn);
db_run! { $conn: sqlite, mysql, postgresql $body } tokio::task::block_in_place(move || $body ) // Run blocking can't be used due to the 'static limitation, use block_in_place instead
}; }};
( @raw $conn:ident: $body:block ) => {
db_run! { @raw $conn: sqlite, mysql, postgresql $body }
};
// Different code for each db
( $conn:ident: $( $($db:ident),+ $body:block )+ ) => {{ ( $conn:ident: $( $($db:ident),+ $body:block )+ ) => {{
#[allow(unused)] use diesel::prelude::*; db_run_base!($conn);
#[allow(unused)] use $crate::db::FromDb; match std::ops::DerefMut::deref_mut($conn) {
$($(
let conn = $conn.conn.clone(); #[cfg($db)]
let mut conn = conn.lock_owned().await; paste::paste!($crate::db::DbConnInner::[<$db:camel>](ref mut $conn)) => {
match conn.as_mut().expect("internal invariant broken: self.connection is Some") { tokio::task::block_in_place(move || $body ) // Run blocking can't be used due to the 'static limitation, use block_in_place instead
$($( },
#[cfg($db)] )+)+}
$crate::db::DbConnInner::$db($conn) => {
paste::paste! {
#[allow(unused)] use $crate::db::[<__ $db _schema>]::{self as schema, *};
#[allow(unused)] use [<__ $db _model>]::*;
}
tokio::task::block_in_place(move || { $body }) // Run blocking can't be used due to the 'static limitation, use block_in_place instead
},
)+)+
}
}};
( @raw $conn:ident: $( $($db:ident),+ $body:block )+ ) => {{
#[allow(unused)] use diesel::prelude::*;
#[allow(unused)] use $crate::db::FromDb;
let conn = $conn.conn.clone();
let mut conn = conn.lock_owned().await;
match conn.as_mut().expect("internal invariant broken: self.connection is Some") {
$($(
#[cfg($db)]
$crate::db::DbConnInner::$db($conn) => {
paste::paste! {
#[allow(unused)] use $crate::db::[<__ $db _schema>]::{self as schema, *};
// @ RAW: #[allow(unused)] use [<__ $db _model>]::*;
}
tokio::task::block_in_place(move || { $body }) // Run blocking can't be used due to the 'static limitation, use block_in_place instead
},
)+)+
}
}}; }};
} }
pub trait FromDb { #[path = "schemas/schema.rs"]
type Output; pub mod schema;
#[allow(clippy::wrong_self_convention)]
fn from_db(self) -> Self::Output;
}
impl<T: FromDb> FromDb for Vec<T> {
type Output = Vec<T::Output>;
#[allow(clippy::wrong_self_convention)]
#[inline(always)]
fn from_db(self) -> Self::Output {
self.into_iter().map(crate::db::FromDb::from_db).collect()
}
}
impl<T: FromDb> FromDb for Option<T> {
type Output = Option<T::Output>;
#[allow(clippy::wrong_self_convention)]
#[inline(always)]
fn from_db(self) -> Self::Output {
self.map(crate::db::FromDb::from_db)
}
}
// 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>] {
#[allow(clippy::wrong_self_convention)]
#[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;
#[allow(clippy::wrong_self_convention)]
#[inline(always)] fn from_db(self) -> Self::Output { super::$name { $( $field: self.$field, )+ } }
}
}
};
}
// Reexport the models, needs to be after the macros are defined so it can access them // Reexport the models, needs to be after the macros are defined so it can access them
pub mod models; pub mod models;
/// Creates a back-up of the sqlite database #[allow(unused_variables)] // Since we do not use `conn` in PostgreSQL and MySQL
/// MySQL/MariaDB and PostgreSQL are not supported. pub async fn backup_database(conn: &DbConn) -> Result<(), Error> {
pub async fn backup_database(conn: &mut DbConn) -> Result<(), Error> { db_run! {conn:
db_run! {@raw conn:
postgresql, mysql { postgresql, mysql {
let _ = conn;
err!("PostgreSQL and MySQL/MariaDB do not support this backup feature"); err!("PostgreSQL and MySQL/MariaDB do not support this backup feature");
} }
sqlite { sqlite {
@ -387,8 +282,8 @@ pub async fn backup_database(conn: &mut DbConn) -> Result<(), Error> {
} }
/// Get the SQL Server version /// Get the SQL Server version
pub async fn get_sql_server_version(conn: &mut DbConn) -> String { pub async fn get_sql_server_version(conn: &DbConn) -> String {
db_run! {@raw conn: db_run! {conn:
postgresql, mysql { postgresql, mysql {
sql_function!{ sql_function!{
fn version() -> diesel::sql_types::Text; fn version() -> diesel::sql_types::Text;
@ -427,13 +322,11 @@ mod sqlite_migrations {
use diesel_migrations::{EmbeddedMigrations, MigrationHarness}; use diesel_migrations::{EmbeddedMigrations, MigrationHarness};
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/sqlite"); pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/sqlite");
pub fn run_migrations() -> Result<(), super::Error> { pub fn run_migrations(url: &str) -> Result<(), super::Error> {
use diesel::{Connection, RunQueryDsl}; use diesel::{Connection, RunQueryDsl};
let url = crate::CONFIG.database_url();
// Establish a connection to the sqlite database (this will create a new one, if it does // Establish a connection to the sqlite database (this will create a new one, if it does
// not exist, and exit if there is an error). // not exist, and exit if there is an error).
let mut connection = diesel::sqlite::SqliteConnection::establish(&url)?; let mut connection = diesel::sqlite::SqliteConnection::establish(url)?;
// Run the migrations after successfully establishing a connection // Run the migrations after successfully establishing a connection
// Disable Foreign Key Checks during migration // Disable Foreign Key Checks during migration
@ -457,10 +350,10 @@ mod mysql_migrations {
use diesel_migrations::{EmbeddedMigrations, MigrationHarness}; use diesel_migrations::{EmbeddedMigrations, MigrationHarness};
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/mysql"); pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/mysql");
pub fn run_migrations() -> Result<(), super::Error> { pub fn run_migrations(url: &str) -> Result<(), super::Error> {
use diesel::{Connection, RunQueryDsl}; use diesel::{Connection, RunQueryDsl};
// Make sure the database is up to date (create if it doesn't exist, or run the migrations) // Make sure the database is up to date (create if it doesn't exist, or run the migrations)
let mut connection = diesel::mysql::MysqlConnection::establish(&crate::CONFIG.database_url())?; let mut connection = diesel::mysql::MysqlConnection::establish(url)?;
// Disable Foreign Key Checks during migration // Disable Foreign Key Checks during migration
// Scoped to a connection/session. // Scoped to a connection/session.
@ -478,10 +371,10 @@ mod postgresql_migrations {
use diesel_migrations::{EmbeddedMigrations, MigrationHarness}; use diesel_migrations::{EmbeddedMigrations, MigrationHarness};
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/postgresql"); pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/postgresql");
pub fn run_migrations() -> Result<(), super::Error> { pub fn run_migrations(url: &str) -> Result<(), super::Error> {
use diesel::Connection; use diesel::Connection;
// Make sure the database is up to date (create if it doesn't exist, or run the migrations) // Make sure the database is up to date (create if it doesn't exist, or run the migrations)
let mut connection = diesel::pg::PgConnection::establish(&crate::CONFIG.database_url())?; let mut connection = diesel::pg::PgConnection::establish(url)?;
connection.run_pending_migrations(MIGRATIONS).expect("Error running migrations"); connection.run_pending_migrations(MIGRATIONS).expect("Error running migrations");
Ok(()) Ok(())
} }

Datei anzeigen

@ -2,20 +2,19 @@ use std::io::ErrorKind;
use serde_json::Value; use serde_json::Value;
use crate::db::schema::attachments;
use crate::CONFIG; use crate::CONFIG;
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = attachments)]
#[diesel(table_name = attachments)] #[diesel(treat_none_as_null = true)]
#[diesel(treat_none_as_null = true)] #[diesel(primary_key(id))]
#[diesel(primary_key(id))] pub struct Attachment {
pub struct Attachment { pub id: String,
pub id: String, pub cipher_uuid: String,
pub cipher_uuid: String, pub file_name: String, // encrypted
pub file_name: String, // encrypted pub file_size: i32,
pub file_size: i32, pub akey: Option<String>,
pub akey: Option<String>,
}
} }
/// Local methods /// Local methods
@ -60,11 +59,11 @@ use crate::error::MapResult;
/// Database methods /// Database methods
impl Attachment { impl Attachment {
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(attachments::table) match diesel::replace_into(attachments::table)
.values(AttachmentDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -72,7 +71,7 @@ impl Attachment {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(attachments::table) diesel::update(attachments::table)
.filter(attachments::id.eq(&self.id)) .filter(attachments::id.eq(&self.id))
.set(AttachmentDb::to_db(self)) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving attachment") .map_res("Error saving attachment")
} }
@ -80,19 +79,18 @@ impl Attachment {
}.map_res("Error saving attachment") }.map_res("Error saving attachment")
} }
postgresql { postgresql {
let value = AttachmentDb::to_db(self);
diesel::insert_into(attachments::table) diesel::insert_into(attachments::table)
.values(&value) .values(self)
.on_conflict(attachments::id) .on_conflict(attachments::id)
.do_update() .do_update()
.set(&value) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving attachment") .map_res("Error saving attachment")
} }
} }
} }
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
crate::util::retry( crate::util::retry(
|| diesel::delete(attachments::table.filter(attachments::id.eq(&self.id))).execute(conn), || diesel::delete(attachments::table.filter(attachments::id.eq(&self.id))).execute(conn),
@ -116,34 +114,32 @@ impl Attachment {
}} }}
} }
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
for attachment in Attachment::find_by_cipher(cipher_uuid, conn).await { for attachment in Attachment::find_by_cipher(cipher_uuid, conn).await {
attachment.delete(conn).await?; attachment.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn find_by_id(id: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_id(id: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.filter(attachments::id.eq(id.to_lowercase())) .filter(attachments::id.eq(id.to_lowercase()))
.first::<AttachmentDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_cipher(cipher_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.filter(attachments::cipher_uuid.eq(cipher_uuid)) .filter(attachments::cipher_uuid.eq(cipher_uuid))
.load::<AttachmentDb>(conn) .load::<Self>(conn)
.expect("Error loading attachments") .expect("Error loading attachments")
.from_db()
}} }}
} }
pub async fn size_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn size_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
let result: Option<i64> = attachments::table let result: Option<i64> = attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
@ -155,7 +151,7 @@ impl Attachment {
}} }}
} }
pub async fn count_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
@ -166,7 +162,7 @@ impl Attachment {
}} }}
} }
pub async fn size_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn size_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
let result: Option<i64> = attachments::table let result: Option<i64> = attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
@ -178,7 +174,7 @@ impl Attachment {
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
@ -192,16 +188,15 @@ impl Attachment {
// This will return all attachments linked to the user or org // This will return all attachments linked to the user or org
// There is no filtering done here if the user actually has access! // There is no filtering done here if the user actually has access!
// It is used to speed up the sync process, and the matching is done in a different part. // It is used to speed up the sync process, and the matching is done in a different part.
pub async fn find_all_by_user_and_orgs(user_uuid: &str, org_uuids: &Vec<String>, conn: &mut DbConn) -> Vec<Self> { pub async fn find_all_by_user_and_orgs(user_uuid: &str, org_uuids: &Vec<String>, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid)))
.filter(ciphers::user_uuid.eq(user_uuid)) .filter(ciphers::user_uuid.eq(user_uuid))
.or_filter(ciphers::organization_uuid.eq_any(org_uuids)) .or_filter(ciphers::organization_uuid.eq_any(org_uuids))
.select(attachments::all_columns) .select(attachments::all_columns)
.load::<AttachmentDb>(conn) .load::<Self>(conn)
.expect("Error loading attachments") .expect("Error loading attachments")
.from_db()
}} }}
} }
} }

Datei anzeigen

@ -1,34 +1,33 @@
use crate::crypto::ct_eq; use crate::crypto::ct_eq;
use crate::db::schema::auth_requests;
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
db_object! { #[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)] #[diesel(table_name = auth_requests)]
#[diesel(table_name = auth_requests)] #[diesel(treat_none_as_null = true)]
#[diesel(treat_none_as_null = true)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct AuthRequest {
pub struct AuthRequest { pub uuid: String,
pub uuid: String, pub user_uuid: String,
pub user_uuid: String, pub organization_uuid: Option<String>,
pub organization_uuid: Option<String>,
pub request_device_identifier: String, pub request_device_identifier: String,
pub device_type: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs pub device_type: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs
pub request_ip: String, pub request_ip: String,
pub response_device_id: Option<String>, pub response_device_id: Option<String>,
pub access_code: String, pub access_code: String,
pub public_key: String, pub public_key: String,
pub enc_key: Option<String>, pub enc_key: Option<String>,
pub master_password_hash: Option<String>, pub master_password_hash: Option<String>,
pub approved: Option<bool>, pub approved: Option<bool>,
pub creation_date: NaiveDateTime, pub creation_date: NaiveDateTime,
pub response_date: Option<NaiveDateTime>, pub response_date: Option<NaiveDateTime>,
pub authentication_date: Option<NaiveDateTime>, pub authentication_date: Option<NaiveDateTime>,
}
} }
impl AuthRequest { impl AuthRequest {
@ -69,11 +68,11 @@ use crate::api::EmptyResult;
use crate::error::MapResult; use crate::error::MapResult;
impl AuthRequest { impl AuthRequest {
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(auth_requests::table) match diesel::replace_into(auth_requests::table)
.values(AuthRequestDb::to_db(self)) .values(&*self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -81,7 +80,7 @@ impl AuthRequest {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(auth_requests::table) diesel::update(auth_requests::table)
.filter(auth_requests::uuid.eq(&self.uuid)) .filter(auth_requests::uuid.eq(&self.uuid))
.set(AuthRequestDb::to_db(self)) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error auth_request") .map_res("Error auth_request")
} }
@ -89,45 +88,43 @@ impl AuthRequest {
}.map_res("Error auth_request") }.map_res("Error auth_request")
} }
postgresql { postgresql {
let value = AuthRequestDb::to_db(self);
diesel::insert_into(auth_requests::table) diesel::insert_into(auth_requests::table)
.values(&value) .values(&*self)
.on_conflict(auth_requests::uuid) .on_conflict(auth_requests::uuid)
.do_update() .do_update()
.set(&value) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving auth_request") .map_res("Error saving auth_request")
} }
} }
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
auth_requests::table auth_requests::table
.filter(auth_requests::uuid.eq(uuid)) .filter(auth_requests::uuid.eq(uuid))
.first::<AuthRequestDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
auth_requests::table auth_requests::table
.filter(auth_requests::user_uuid.eq(user_uuid)) .filter(auth_requests::user_uuid.eq(user_uuid))
.load::<AuthRequestDb>(conn).expect("Error loading auth_requests").from_db() .load::<Self>(conn).expect("Error loading auth_requests")
}} }}
} }
pub async fn find_created_before(dt: &NaiveDateTime, conn: &mut DbConn) -> Vec<Self> { pub async fn find_created_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
auth_requests::table auth_requests::table
.filter(auth_requests::creation_date.lt(dt)) .filter(auth_requests::creation_date.lt(dt))
.load::<AuthRequestDb>(conn).expect("Error loading auth_requests").from_db() .load::<Self>(conn).expect("Error loading auth_requests")
}} }}
} }
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(auth_requests::table.filter(auth_requests::uuid.eq(&self.uuid))) diesel::delete(auth_requests::table.filter(auth_requests::uuid.eq(&self.uuid)))
.execute(conn) .execute(conn)
@ -139,7 +136,7 @@ impl AuthRequest {
ct_eq(&self.access_code, access_code) ct_eq(&self.access_code, access_code)
} }
pub async fn purge_expired_auth_requests(conn: &mut DbConn) { pub async fn purge_expired_auth_requests(conn: &DbConn) {
let expiry_time = Utc::now().naive_utc() - chrono::Duration::minutes(5); //after 5 minutes, clients reject the request let expiry_time = Utc::now().naive_utc() - chrono::Duration::minutes(5); //after 5 minutes, clients reject the request
for auth_request in Self::find_created_before(&expiry_time, conn).await { for auth_request in Self::find_created_before(&expiry_time, conn).await {
auth_request.delete(conn).await.ok(); auth_request.delete(conn).await.ok();

Datei anzeigen

@ -2,45 +2,42 @@ use crate::CONFIG;
use chrono::{Duration, NaiveDateTime, Utc}; use chrono::{Duration, NaiveDateTime, Utc};
use serde_json::Value; use serde_json::Value;
use super::{ use super::{Attachment, CollectionCipher, Favorite, FolderCipher, Group, User, UserOrganization};
Attachment, CollectionCipher, Favorite, FolderCipher, Group, User, UserOrgStatus, UserOrgType, UserOrganization,
};
use crate::api::core::{CipherData, CipherSyncData, CipherSyncType}; use crate::api::core::{CipherData, CipherSyncData, CipherSyncType};
use crate::db::schema::ciphers;
use std::borrow::Cow; use std::borrow::Cow;
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = ciphers)]
#[diesel(table_name = ciphers)] #[diesel(treat_none_as_null = true)]
#[diesel(treat_none_as_null = true)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct Cipher {
pub struct Cipher { pub uuid: String,
pub uuid: String, pub created_at: NaiveDateTime,
pub created_at: NaiveDateTime, pub updated_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,
Fido2key = 5 Fido2key = 5
*/ */
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>,
pub reprompt: Option<i32>, pub reprompt: Option<i32>,
}
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -116,7 +113,7 @@ impl Cipher {
user_uuid: &str, user_uuid: &str,
cipher_sync_data: Option<&CipherSyncData>, cipher_sync_data: Option<&CipherSyncData>,
sync_type: CipherSyncType, sync_type: CipherSyncType,
conn: &mut DbConn, conn: &DbConn,
) -> Value { ) -> Value {
use crate::util::format_date; use crate::util::format_date;
@ -261,7 +258,7 @@ impl Cipher {
json_object json_object
} }
pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec<String> { pub async fn update_users_revision(&self, conn: &DbConn) -> Vec<String> {
let mut user_uuids = Vec::new(); let mut user_uuids = Vec::new();
match self.user_uuid { match self.user_uuid {
Some(ref user_uuid) => { Some(ref user_uuid) => {
@ -281,14 +278,14 @@ impl Cipher {
user_uuids user_uuids
} }
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
self.update_users_revision(conn).await; self.update_users_revision(conn).await;
self.updated_at = Utc::now().naive_utc(); self.updated_at = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(ciphers::table) match diesel::replace_into(ciphers::table)
.values(CipherDb::to_db(self)) .values(&*self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -296,7 +293,7 @@ impl Cipher {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(ciphers::table) diesel::update(ciphers::table)
.filter(ciphers::uuid.eq(&self.uuid)) .filter(ciphers::uuid.eq(&self.uuid))
.set(CipherDb::to_db(self)) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving cipher") .map_res("Error saving cipher")
} }
@ -304,19 +301,18 @@ impl Cipher {
}.map_res("Error saving cipher") }.map_res("Error saving cipher")
} }
postgresql { postgresql {
let value = CipherDb::to_db(self);
diesel::insert_into(ciphers::table) diesel::insert_into(ciphers::table)
.values(&value) .values(&*self)
.on_conflict(ciphers::uuid) .on_conflict(ciphers::uuid)
.do_update() .do_update()
.set(&value) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving cipher") .map_res("Error saving cipher")
} }
} }
} }
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
self.update_users_revision(conn).await; self.update_users_revision(conn).await;
FolderCipher::delete_all_by_cipher(&self.uuid, conn).await?; FolderCipher::delete_all_by_cipher(&self.uuid, conn).await?;
@ -331,7 +327,7 @@ impl Cipher {
}} }}
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
// TODO: Optimize this by executing a DELETE directly on the database, instead of first fetching. // TODO: Optimize this by executing a DELETE directly on the database, instead of first fetching.
for cipher in Self::find_by_org(org_uuid, conn).await { for cipher in Self::find_by_org(org_uuid, conn).await {
cipher.delete(conn).await?; cipher.delete(conn).await?;
@ -339,7 +335,7 @@ impl Cipher {
Ok(()) Ok(())
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
for cipher in Self::find_owned_by_user(user_uuid, conn).await { for cipher in Self::find_owned_by_user(user_uuid, conn).await {
cipher.delete(conn).await?; cipher.delete(conn).await?;
} }
@ -347,7 +343,7 @@ impl Cipher {
} }
/// Purge all ciphers that are old enough to be auto-deleted. /// Purge all ciphers that are old enough to be auto-deleted.
pub async fn purge_trash(conn: &mut DbConn) { pub async fn purge_trash(conn: &DbConn) {
if let Some(auto_delete_days) = CONFIG.trash_auto_delete_days() { if let Some(auto_delete_days) = CONFIG.trash_auto_delete_days() {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
let dt = now - Duration::days(auto_delete_days); let dt = now - Duration::days(auto_delete_days);
@ -357,7 +353,7 @@ impl Cipher {
} }
} }
pub async fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(user_uuid, conn).await; User::update_uuid_revision(user_uuid, conn).await;
match (self.get_folder_uuid(user_uuid, conn).await, folder_uuid) { match (self.get_folder_uuid(user_uuid, conn).await, folder_uuid) {
@ -394,7 +390,7 @@ impl Cipher {
&self, &self,
user_uuid: &str, user_uuid: &str,
cipher_sync_data: Option<&CipherSyncData>, cipher_sync_data: Option<&CipherSyncData>,
conn: &mut DbConn, conn: &DbConn,
) -> bool { ) -> bool {
if let Some(ref org_uuid) = self.organization_uuid { if let Some(ref org_uuid) = self.organization_uuid {
if let Some(cipher_sync_data) = cipher_sync_data { if let Some(cipher_sync_data) = cipher_sync_data {
@ -413,7 +409,7 @@ impl Cipher {
&self, &self,
user_uuid: &str, user_uuid: &str,
cipher_sync_data: Option<&CipherSyncData>, cipher_sync_data: Option<&CipherSyncData>,
conn: &mut DbConn, conn: &DbConn,
) -> bool { ) -> bool {
if let Some(ref org_uuid) = self.organization_uuid { if let Some(ref org_uuid) = self.organization_uuid {
if let Some(cipher_sync_data) = cipher_sync_data { if let Some(cipher_sync_data) = cipher_sync_data {
@ -434,7 +430,7 @@ impl Cipher {
&self, &self,
user_uuid: &str, user_uuid: &str,
cipher_sync_data: Option<&CipherSyncData>, cipher_sync_data: Option<&CipherSyncData>,
conn: &mut DbConn, conn: &DbConn,
) -> Option<(bool, bool)> { ) -> Option<(bool, bool)> {
// Check whether this cipher is directly owned by the user, or is in // Check whether this cipher is directly owned by the user, or is in
// a collection that the user has full access to. If so, there are no // a collection that the user has full access to. If so, there are no
@ -492,7 +488,7 @@ impl Cipher {
Some((read_only, hide_passwords)) Some((read_only, hide_passwords))
} }
async fn get_user_collections_access_flags(&self, user_uuid: &str, conn: &mut DbConn) -> Vec<(bool, bool)> { async fn get_user_collections_access_flags(&self, user_uuid: &str, conn: &DbConn) -> Vec<(bool, bool)> {
db_run! {conn: { db_run! {conn: {
// Check whether this cipher is in any collections accessible to the // Check whether this cipher is in any collections accessible to the
// user. If so, retrieve the access flags for each collection. // user. If so, retrieve the access flags for each collection.
@ -509,7 +505,7 @@ impl Cipher {
}} }}
} }
async fn get_group_collections_access_flags(&self, user_uuid: &str, conn: &mut DbConn) -> Vec<(bool, bool)> { async fn get_group_collections_access_flags(&self, user_uuid: &str, conn: &DbConn) -> Vec<(bool, bool)> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::uuid.eq(&self.uuid)) .filter(ciphers::uuid.eq(&self.uuid))
@ -532,31 +528,31 @@ impl Cipher {
}} }}
} }
pub async fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
match self.get_access_restrictions(user_uuid, None, conn).await { match self.get_access_restrictions(user_uuid, None, conn).await {
Some((read_only, _hide_passwords)) => !read_only, Some((read_only, _hide_passwords)) => !read_only,
None => false, None => false,
} }
} }
pub async fn is_accessible_to_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
self.get_access_restrictions(user_uuid, None, conn).await.is_some() self.get_access_restrictions(user_uuid, None, conn).await.is_some()
} }
// Returns whether this cipher is a favorite of the specified user. // Returns whether this cipher is a favorite of the specified user.
pub async fn is_favorite(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_favorite(&self, user_uuid: &str, conn: &DbConn) -> bool {
Favorite::is_favorite(&self.uuid, user_uuid, conn).await Favorite::is_favorite(&self.uuid, user_uuid, conn).await
} }
// Sets whether this cipher is a favorite of the specified user. // Sets whether this cipher is a favorite of the specified user.
pub async fn set_favorite(&self, favorite: Option<bool>, user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn set_favorite(&self, favorite: Option<bool>, user_uuid: &str, conn: &DbConn) -> EmptyResult {
match favorite { match favorite {
None => Ok(()), // No change requested. None => Ok(()), // No change requested.
Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn).await, Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn).await,
} }
} }
pub async fn get_folder_uuid(&self, user_uuid: &str, conn: &mut DbConn) -> Option<String> { pub async fn get_folder_uuid(&self, user_uuid: &str, conn: &DbConn) -> Option<String> {
db_run! {conn: { db_run! {conn: {
folders_ciphers::table folders_ciphers::table
.inner_join(folders::table) .inner_join(folders::table)
@ -568,13 +564,12 @@ impl Cipher {
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::uuid.eq(uuid)) .filter(ciphers::uuid.eq(uuid))
.first::<CipherDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
@ -590,7 +585,7 @@ impl Cipher {
// true, then the non-interesting ciphers will not be returned. As a // true, then the non-interesting ciphers will not be returned. As a
// result, those ciphers will not appear in "My Vault" for the org // result, those ciphers will not appear in "My Vault" for the org
// owner/admin, but they can still be accessed via the org vault view. // owner/admin, but they can still be accessed via the org vault view.
pub async fn find_by_user(user_uuid: &str, visible_only: bool, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &str, visible_only: bool, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
let mut query = ciphers::table let mut query = ciphers::table
.left_join(ciphers_collections::table.on( .left_join(ciphers_collections::table.on(
@ -633,28 +628,28 @@ impl Cipher {
query query
.select(ciphers::all_columns) .select(ciphers::all_columns)
.distinct() .distinct()
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db() .load::<Self>(conn).expect("Error loading ciphers")
}} }}
} }
// Find all ciphers visible to the specified user. // Find all ciphers visible to the specified user.
pub async fn find_by_user_visible(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user_visible(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
Self::find_by_user(user_uuid, true, conn).await Self::find_by_user(user_uuid, true, conn).await
} }
// Find all ciphers directly owned by the specified user. // Find all ciphers directly owned by the specified user.
pub async fn find_owned_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_owned_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter( .filter(
ciphers::user_uuid.eq(user_uuid) ciphers::user_uuid.eq(user_uuid)
.and(ciphers::organization_uuid.is_null()) .and(ciphers::organization_uuid.is_null())
) )
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db() .load::<Self>(conn).expect("Error loading ciphers")
}} }}
} }
pub async fn count_owned_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::user_uuid.eq(user_uuid)) .filter(ciphers::user_uuid.eq(user_uuid))
@ -665,15 +660,15 @@ impl Cipher {
}} }}
} }
pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::organization_uuid.eq(org_uuid)) .filter(ciphers::organization_uuid.eq(org_uuid))
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db() .load::<Self>(conn).expect("Error loading ciphers")
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::organization_uuid.eq(org_uuid)) .filter(ciphers::organization_uuid.eq(org_uuid))
@ -684,25 +679,25 @@ impl Cipher {
}} }}
} }
pub async fn find_by_folder(folder_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
folders_ciphers::table.inner_join(ciphers::table) folders_ciphers::table.inner_join(ciphers::table)
.filter(folders_ciphers::folder_uuid.eq(folder_uuid)) .filter(folders_ciphers::folder_uuid.eq(folder_uuid))
.select(ciphers::all_columns) .select(ciphers::all_columns)
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db() .load::<Self>(conn).expect("Error loading ciphers")
}} }}
} }
/// Find all ciphers that were deleted before the specified datetime. /// Find all ciphers that were deleted before the specified datetime.
pub async fn find_deleted_before(dt: &NaiveDateTime, conn: &mut DbConn) -> Vec<Self> { pub async fn find_deleted_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::deleted_at.lt(dt)) .filter(ciphers::deleted_at.lt(dt))
.load::<CipherDb>(conn).expect("Error loading ciphers").from_db() .load::<Self>(conn).expect("Error loading ciphers")
}} }}
} }
pub async fn get_collections(&self, user_id: String, conn: &mut DbConn) -> Vec<String> { pub async fn get_collections(&self, user_id: String, conn: &DbConn) -> Vec<String> {
db_run! {conn: { db_run! {conn: {
ciphers_collections::table ciphers_collections::table
.inner_join(collections::table.on( .inner_join(collections::table.on(
@ -731,7 +726,7 @@ impl Cipher {
/// Return a Vec with (cipher_uuid, collection_uuid) /// Return a Vec with (cipher_uuid, collection_uuid)
/// This is used during a full sync so we only need one query for all collections accessible. /// This is used during a full sync so we only need one query for all collections accessible.
pub async fn get_collections_with_cipher_by_user(user_id: String, conn: &mut DbConn) -> Vec<(String, String)> { pub async fn get_collections_with_cipher_by_user(user_id: String, conn: &DbConn) -> Vec<(String, String)> {
db_run! {conn: { db_run! {conn: {
ciphers_collections::table ciphers_collections::table
.inner_join(collections::table.on( .inner_join(collections::table.on(

Datei anzeigen

@ -1,35 +1,35 @@
use serde_json::Value; use serde_json::Value;
use super::{CollectionGroup, User, UserOrgStatus, UserOrgType, UserOrganization}; use super::{CollectionGroup, User, UserOrganization};
db_object! { use crate::db::schema::{ciphers_collections, collections, users_collections};
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = collections)]
#[diesel(primary_key(uuid))]
pub struct Collection {
pub uuid: String,
pub org_uuid: String,
pub name: String,
pub external_id: Option<String>,
}
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = users_collections)] #[diesel(table_name = collections)]
#[diesel(primary_key(user_uuid, collection_uuid))] #[diesel(primary_key(uuid))]
pub struct CollectionUser { pub struct Collection {
pub user_uuid: String, pub uuid: String,
pub collection_uuid: String, pub org_uuid: String,
pub read_only: bool, pub name: String,
pub hide_passwords: bool, pub external_id: Option<String>,
} }
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
#[diesel(table_name = ciphers_collections)] #[diesel(table_name = users_collections)]
#[diesel(primary_key(cipher_uuid, collection_uuid))] #[diesel(primary_key(user_uuid, collection_uuid))]
pub struct CollectionCipher { pub struct CollectionUser {
pub cipher_uuid: String, pub user_uuid: String,
pub collection_uuid: String, pub collection_uuid: String,
} pub read_only: bool,
pub hide_passwords: bool,
}
#[derive(Identifiable, Queryable, Insertable)]
#[diesel(table_name = ciphers_collections)]
#[diesel(primary_key(cipher_uuid, collection_uuid))]
pub struct CollectionCipher {
pub cipher_uuid: String,
pub collection_uuid: String,
} }
/// Local methods /// Local methods
@ -75,7 +75,7 @@ impl Collection {
&self, &self,
user_uuid: &str, user_uuid: &str,
cipher_sync_data: Option<&crate::api::core::CipherSyncData>, cipher_sync_data: Option<&crate::api::core::CipherSyncData>,
conn: &mut DbConn, conn: &DbConn,
) -> Value { ) -> Value {
let (read_only, hide_passwords) = if let Some(cipher_sync_data) = cipher_sync_data { let (read_only, hide_passwords) = if let Some(cipher_sync_data) = cipher_sync_data {
match cipher_sync_data.user_organizations.get(&self.org_uuid) { match cipher_sync_data.user_organizations.get(&self.org_uuid) {
@ -110,13 +110,13 @@ use crate::error::MapResult;
/// Database methods /// Database methods
impl Collection { impl Collection {
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
self.update_users_revision(conn).await; self.update_users_revision(conn).await;
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(collections::table) match diesel::replace_into(collections::table)
.values(CollectionDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -124,7 +124,7 @@ impl Collection {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(collections::table) diesel::update(collections::table)
.filter(collections::uuid.eq(&self.uuid)) .filter(collections::uuid.eq(&self.uuid))
.set(CollectionDb::to_db(self)) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving collection") .map_res("Error saving collection")
} }
@ -132,19 +132,18 @@ impl Collection {
}.map_res("Error saving collection") }.map_res("Error saving collection")
} }
postgresql { postgresql {
let value = CollectionDb::to_db(self);
diesel::insert_into(collections::table) diesel::insert_into(collections::table)
.values(&value) .values(self)
.on_conflict(collections::uuid) .on_conflict(collections::uuid)
.do_update() .do_update()
.set(&value) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving collection") .map_res("Error saving collection")
} }
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
self.update_users_revision(conn).await; self.update_users_revision(conn).await;
CollectionCipher::delete_all_by_collection(&self.uuid, conn).await?; CollectionCipher::delete_all_by_collection(&self.uuid, conn).await?;
CollectionUser::delete_all_by_collection(&self.uuid, conn).await?; CollectionUser::delete_all_by_collection(&self.uuid, conn).await?;
@ -157,30 +156,29 @@ impl Collection {
}} }}
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
for collection in Self::find_by_organization(org_uuid, conn).await { for collection in Self::find_by_organization(org_uuid, conn).await {
collection.delete(conn).await?; collection.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn update_users_revision(&self, conn: &mut DbConn) { pub async fn update_users_revision(&self, conn: &DbConn) {
for user_org in UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).await.iter() { for user_org in UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).await.iter() {
User::update_uuid_revision(&user_org.user_uuid, conn).await; User::update_uuid_revision(&user_org.user_uuid, conn).await;
} }
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.filter(collections::uuid.eq(uuid)) .filter(collections::uuid.eq(uuid))
.first::<CollectionDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_user_uuid(user_uuid: String, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user_uuid(user_uuid: String, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.left_join(users_collections::table.on( .left_join(users_collections::table.on(
@ -220,22 +218,18 @@ impl Collection {
) )
.select(collections::all_columns) .select(collections::all_columns)
.distinct() .distinct()
.load::<CollectionDb>(conn).expect("Error loading collections").from_db() .load::<Self>(conn).expect("Error loading collections")
}} }}
} }
// Check if a user has access to a specific collection // Check if a user has access to a specific collection
// FIXME: This needs to be reviewed. The query used by `find_by_user_uuid` could be adjusted to filter when needed. // FIXME: This needs to be reviewed. The query used by `find_by_user_uuid` could be adjusted to filter when needed.
// For now this is a good solution without making to much changes. // For now this is a good solution without making to much changes.
pub async fn has_access_by_collection_and_user_uuid( pub async fn has_access_by_collection_and_user_uuid(collection_uuid: &str, user_uuid: &str, conn: &DbConn) -> bool {
collection_uuid: &str,
user_uuid: &str,
conn: &mut DbConn,
) -> bool {
Self::find_by_user_uuid(user_uuid.to_owned(), conn).await.into_iter().any(|c| c.uuid == collection_uuid) Self::find_by_user_uuid(user_uuid.to_owned(), conn).await.into_iter().any(|c| c.uuid == collection_uuid)
} }
pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
Self::find_by_user_uuid(user_uuid.to_owned(), conn) Self::find_by_user_uuid(user_uuid.to_owned(), conn)
.await .await
.into_iter() .into_iter()
@ -243,17 +237,16 @@ impl Collection {
.collect() .collect()
} }
pub async fn find_by_organization(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.filter(collections::org_uuid.eq(org_uuid)) .filter(collections::org_uuid.eq(org_uuid))
.load::<CollectionDb>(conn) .load::<Self>(conn)
.expect("Error loading collections") .expect("Error loading collections")
.from_db()
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.filter(collections::org_uuid.eq(org_uuid)) .filter(collections::org_uuid.eq(org_uuid))
@ -264,19 +257,18 @@ impl Collection {
}} }}
} }
pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.filter(collections::uuid.eq(uuid)) .filter(collections::uuid.eq(uuid))
.filter(collections::org_uuid.eq(org_uuid)) .filter(collections::org_uuid.eq(org_uuid))
.select(collections::all_columns) .select(collections::all_columns)
.first::<CollectionDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: String, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: String, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
collections::table collections::table
.left_join(users_collections::table.on( .left_join(users_collections::table.on(
@ -313,12 +305,11 @@ impl Collection {
) )
) )
).select(collections::all_columns) ).select(collections::all_columns)
.first::<CollectionDb>(conn).ok() .first::<Self>(conn).ok()
.from_db()
}} }}
} }
pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
let user_uuid = user_uuid.to_string(); let user_uuid = user_uuid.to_string();
db_run! { conn: { db_run! { conn: {
collections::table collections::table
@ -364,7 +355,7 @@ impl Collection {
}} }}
} }
pub async fn hide_passwords_for_user(&self, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn hide_passwords_for_user(&self, user_uuid: &str, conn: &DbConn) -> bool {
let user_uuid = user_uuid.to_string(); let user_uuid = user_uuid.to_string();
db_run! { conn: { db_run! { conn: {
collections::table collections::table
@ -413,29 +404,27 @@ impl Collection {
/// Database methods /// Database methods
impl CollectionUser { impl CollectionUser {
pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_collections::table users_collections::table
.filter(users_collections::user_uuid.eq(user_uuid)) .filter(users_collections::user_uuid.eq(user_uuid))
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid))) .inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
.filter(collections::org_uuid.eq(org_uuid)) .filter(collections::org_uuid.eq(org_uuid))
.select(users_collections::all_columns) .select(users_collections::all_columns)
.load::<CollectionUserDb>(conn) .load::<Self>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
.from_db()
}} }}
} }
pub async fn find_by_organization(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_collections::table users_collections::table
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid))) .inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
.filter(collections::org_uuid.eq(org_uuid)) .filter(collections::org_uuid.eq(org_uuid))
.inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid))) .inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid)))
.select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords)) .select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords))
.load::<CollectionUserDb>(conn) .load::<Self>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
.from_db()
}} }}
} }
@ -444,7 +433,7 @@ impl CollectionUser {
collection_uuid: &str, collection_uuid: &str,
read_only: bool, read_only: bool,
hide_passwords: bool, hide_passwords: bool,
conn: &mut DbConn, conn: &DbConn,
) -> EmptyResult { ) -> EmptyResult {
User::update_uuid_revision(user_uuid, conn).await; User::update_uuid_revision(user_uuid, conn).await;
@ -497,7 +486,7 @@ impl CollectionUser {
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&self.user_uuid, conn).await; User::update_uuid_revision(&self.user_uuid, conn).await;
db_run! { conn: { db_run! { conn: {
@ -511,60 +500,52 @@ impl CollectionUser {
}} }}
} }
pub async fn find_by_collection(collection_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_collections::table users_collections::table
.filter(users_collections::collection_uuid.eq(collection_uuid)) .filter(users_collections::collection_uuid.eq(collection_uuid))
.select(users_collections::all_columns) .select(users_collections::all_columns)
.load::<CollectionUserDb>(conn) .load::<Self>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
.from_db()
}} }}
} }
pub async fn find_by_collection_swap_user_uuid_with_org_user_uuid( pub async fn find_by_collection_swap_user_uuid_with_org_user_uuid(
collection_uuid: &str, collection_uuid: &str,
conn: &mut DbConn, conn: &DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_collections::table users_collections::table
.filter(users_collections::collection_uuid.eq(collection_uuid)) .filter(users_collections::collection_uuid.eq(collection_uuid))
.inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid))) .inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid)))
.select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords)) .select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords))
.load::<CollectionUserDb>(conn) .load::<Self>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
.from_db()
}} }}
} }
pub async fn find_by_collection_and_user( pub async fn find_by_collection_and_user(collection_uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> {
collection_uuid: &str,
user_uuid: &str,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
users_collections::table users_collections::table
.filter(users_collections::collection_uuid.eq(collection_uuid)) .filter(users_collections::collection_uuid.eq(collection_uuid))
.filter(users_collections::user_uuid.eq(user_uuid)) .filter(users_collections::user_uuid.eq(user_uuid))
.select(users_collections::all_columns) .select(users_collections::all_columns)
.first::<CollectionUserDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_collections::table users_collections::table
.filter(users_collections::user_uuid.eq(user_uuid)) .filter(users_collections::user_uuid.eq(user_uuid))
.select(users_collections::all_columns) .select(users_collections::all_columns)
.load::<CollectionUserDb>(conn) .load::<Self>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
.from_db()
}} }}
} }
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
for collection in CollectionUser::find_by_collection(collection_uuid, conn).await.iter() { for collection in CollectionUser::find_by_collection(collection_uuid, conn).await.iter() {
User::update_uuid_revision(&collection.user_uuid, conn).await; User::update_uuid_revision(&collection.user_uuid, conn).await;
} }
@ -576,7 +557,7 @@ impl CollectionUser {
}} }}
} }
pub async fn delete_all_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> EmptyResult {
let collectionusers = Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn).await; let collectionusers = Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn).await;
db_run! { conn: { db_run! { conn: {
@ -595,7 +576,7 @@ impl CollectionUser {
/// Database methods /// Database methods
impl CollectionCipher { impl CollectionCipher {
pub async fn save(cipher_uuid: &str, collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult {
Self::update_users_revision(collection_uuid, conn).await; Self::update_users_revision(collection_uuid, conn).await;
db_run! { conn: db_run! { conn:
@ -625,7 +606,7 @@ impl CollectionCipher {
} }
} }
pub async fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult {
Self::update_users_revision(collection_uuid, conn).await; Self::update_users_revision(collection_uuid, conn).await;
db_run! { conn: { db_run! { conn: {
@ -639,7 +620,7 @@ impl CollectionCipher {
}} }}
} }
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))) diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)))
.execute(conn) .execute(conn)
@ -647,7 +628,7 @@ impl CollectionCipher {
}} }}
} }
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid))) diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid)))
.execute(conn) .execute(conn)
@ -655,7 +636,7 @@ impl CollectionCipher {
}} }}
} }
pub async fn update_users_revision(collection_uuid: &str, conn: &mut DbConn) { pub async fn update_users_revision(collection_uuid: &str, conn: &DbConn) {
if let Some(collection) = Collection::find_by_uuid(collection_uuid, conn).await { if let Some(collection) = Collection::find_by_uuid(collection_uuid, conn).await {
collection.update_users_revision(conn).await; collection.update_users_revision(conn).await;
} }

Datei anzeigen

@ -1,29 +1,28 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use crate::db::schema::devices;
use crate::{crypto, CONFIG}; use crate::{crypto, CONFIG};
use core::fmt; use core::fmt;
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = devices)]
#[diesel(table_name = devices)] #[diesel(treat_none_as_null = true)]
#[diesel(treat_none_as_null = true)] #[diesel(primary_key(uuid, user_uuid))]
#[diesel(primary_key(uuid, user_uuid))] pub struct Device {
pub struct Device { pub uuid: String,
pub uuid: String, pub created_at: NaiveDateTime,
pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub user_uuid: String, pub user_uuid: String,
pub name: String, pub name: String,
pub atype: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs pub atype: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs
pub push_uuid: Option<String>, pub push_uuid: Option<String>,
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
@ -115,27 +114,26 @@ use crate::error::MapResult;
/// Database methods /// Database methods
impl Device { impl Device {
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
self.updated_at = Utc::now().naive_utc(); self.updated_at = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
crate::util::retry( crate::util::retry(
|| diesel::replace_into(devices::table).values(DeviceDb::to_db(self)).execute(conn), || diesel::replace_into(devices::table).values(&*self).execute(conn),
10, 10,
).map_res("Error saving device") ).map_res("Error saving device")
} }
postgresql { postgresql {
let value = DeviceDb::to_db(self);
crate::util::retry( crate::util::retry(
|| diesel::insert_into(devices::table).values(&value).on_conflict((devices::uuid, devices::user_uuid)).do_update().set(&value).execute(conn), || diesel::insert_into(devices::table).values(&*self).on_conflict((devices::uuid, devices::user_uuid)).do_update().set(&*self).execute(conn),
10, 10,
).map_res("Error saving device") ).map_res("Error saving device")
} }
} }
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(devices::table.filter(devices::user_uuid.eq(user_uuid))) diesel::delete(devices::table.filter(devices::user_uuid.eq(user_uuid)))
.execute(conn) .execute(conn)
@ -143,38 +141,35 @@ impl Device {
}} }}
} }
pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::uuid.eq(uuid)) .filter(devices::uuid.eq(uuid))
.filter(devices::user_uuid.eq(user_uuid)) .filter(devices::user_uuid.eq(user_uuid))
.first::<DeviceDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::user_uuid.eq(user_uuid)) .filter(devices::user_uuid.eq(user_uuid))
.load::<DeviceDb>(conn) .load::<Self>(conn)
.expect("Error loading devices") .expect("Error loading devices")
.from_db()
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::uuid.eq(uuid)) .filter(devices::uuid.eq(uuid))
.first::<DeviceDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn clear_push_token_by_uuid(uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn clear_push_token_by_uuid(uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::update(devices::table) diesel::update(devices::table)
.filter(devices::uuid.eq(uuid)) .filter(devices::uuid.eq(uuid))
@ -183,38 +178,35 @@ impl Device {
.map_res("Error removing push token") .map_res("Error removing push token")
}} }}
} }
pub async fn find_by_refresh_token(refresh_token: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_refresh_token(refresh_token: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::refresh_token.eq(refresh_token)) .filter(devices::refresh_token.eq(refresh_token))
.first::<DeviceDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_latest_active_by_user(user_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_latest_active_by_user(user_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::user_uuid.eq(user_uuid)) .filter(devices::user_uuid.eq(user_uuid))
.order(devices::updated_at.desc()) .order(devices::updated_at.desc())
.first::<DeviceDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_push_devices_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_push_devices_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::user_uuid.eq(user_uuid)) .filter(devices::user_uuid.eq(user_uuid))
.filter(devices::push_token.is_not_null()) .filter(devices::push_token.is_not_null())
.load::<DeviceDb>(conn) .load::<Self>(conn)
.expect("Error loading push devices") .expect("Error loading push devices")
.from_db()
}} }}
} }
pub async fn check_user_has_push_device(user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn check_user_has_push_device(user_uuid: &str, conn: &DbConn) -> bool {
db_run! { conn: { db_run! { conn: {
devices::table devices::table
.filter(devices::user_uuid.eq(user_uuid)) .filter(devices::user_uuid.eq(user_uuid))

Datei anzeigen

@ -4,26 +4,25 @@ use serde_json::Value;
use crate::{api::EmptyResult, db::DbConn, error::MapResult}; use crate::{api::EmptyResult, db::DbConn, error::MapResult};
use super::User; use super::User;
use crate::db::schema::emergency_access;
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = emergency_access)]
#[diesel(table_name = emergency_access)] #[diesel(treat_none_as_null = true)]
#[diesel(treat_none_as_null = true)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct EmergencyAccess {
pub struct EmergencyAccess { pub uuid: String,
pub uuid: String, pub grantor_uuid: String,
pub grantor_uuid: String, pub grantee_uuid: Option<String>,
pub grantee_uuid: Option<String>, pub email: Option<String>,
pub email: Option<String>, pub key_encrypted: Option<String>,
pub key_encrypted: Option<String>, pub atype: i32, //EmergencyAccessType
pub atype: i32, //EmergencyAccessType pub status: i32, //EmergencyAccessStatus
pub status: i32, //EmergencyAccessStatus pub wait_time_days: i32,
pub wait_time_days: i32, pub recovery_initiated_at: Option<NaiveDateTime>,
pub recovery_initiated_at: Option<NaiveDateTime>, pub last_notification_at: Option<NaiveDateTime>,
pub last_notification_at: Option<NaiveDateTime>, pub updated_at: NaiveDateTime,
pub updated_at: NaiveDateTime, pub created_at: NaiveDateTime,
pub created_at: NaiveDateTime,
}
} }
/// Local methods /// Local methods
@ -66,7 +65,7 @@ impl EmergencyAccess {
}) })
} }
pub async fn to_json_grantor_details(&self, conn: &mut DbConn) -> Value { pub async fn to_json_grantor_details(&self, conn: &DbConn) -> Value {
let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).await.expect("Grantor user not found."); let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).await.expect("Grantor user not found.");
json!({ json!({
@ -81,7 +80,7 @@ impl EmergencyAccess {
}) })
} }
pub async fn to_json_grantee_details(&self, conn: &mut DbConn) -> Value { pub async fn to_json_grantee_details(&self, conn: &DbConn) -> Value {
let grantee_user = if let Some(grantee_uuid) = self.grantee_uuid.as_deref() { let grantee_user = if let Some(grantee_uuid) = self.grantee_uuid.as_deref() {
Some(User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")) Some(User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found."))
} else if let Some(email) = self.email.as_deref() { } else if let Some(email) = self.email.as_deref() {
@ -130,22 +129,22 @@ pub enum EmergencyAccessStatus {
// region Database methods // region Database methods
impl EmergencyAccess { impl EmergencyAccess {
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&self.grantor_uuid, conn).await; User::update_uuid_revision(&self.grantor_uuid, conn).await;
self.updated_at = Utc::now().naive_utc(); self.updated_at = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(emergency_access::table) match diesel::replace_into(schema::emergency_access::table)
.values(EmergencyAccessDb::to_db(self)) .values(&*self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
// Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(emergency_access::table) diesel::update(schema::emergency_access::table)
.filter(emergency_access::uuid.eq(&self.uuid)) .filter(schema::emergency_access::uuid.eq(&self.uuid))
.set(EmergencyAccessDb::to_db(self)) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error updating emergency access") .map_res("Error updating emergency access")
} }
@ -153,12 +152,11 @@ impl EmergencyAccess {
}.map_res("Error saving emergency access") }.map_res("Error saving emergency access")
} }
postgresql { postgresql {
let value = EmergencyAccessDb::to_db(self); diesel::insert_into(schema::emergency_access::table)
diesel::insert_into(emergency_access::table) .values(&*self)
.values(&value) .on_conflict(schema::emergency_access::uuid)
.on_conflict(emergency_access::uuid)
.do_update() .do_update()
.set(&value) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving emergency access") .map_res("Error saving emergency access")
} }
@ -169,7 +167,7 @@ impl EmergencyAccess {
&mut self, &mut self,
status: i32, status: i32,
date: &NaiveDateTime, date: &NaiveDateTime,
conn: &mut DbConn, conn: &DbConn,
) -> EmptyResult { ) -> EmptyResult {
// Update the grantee so that it will refresh it's status. // Update the grantee so that it will refresh it's status.
User::update_uuid_revision(self.grantee_uuid.as_ref().expect("Error getting grantee"), conn).await; User::update_uuid_revision(self.grantee_uuid.as_ref().expect("Error getting grantee"), conn).await;
@ -178,33 +176,29 @@ impl EmergencyAccess {
db_run! {conn: { db_run! {conn: {
crate::util::retry(|| { crate::util::retry(|| {
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid))) diesel::update(schema::emergency_access::table.filter(schema::emergency_access::uuid.eq(&self.uuid)))
.set((emergency_access::status.eq(status), emergency_access::updated_at.eq(date))) .set((schema::emergency_access::status.eq(status), schema::emergency_access::updated_at.eq(date)))
.execute(conn) .execute(conn)
}, 10) }, 10)
.map_res("Error updating emergency access status") .map_res("Error updating emergency access status")
}} }}
} }
pub async fn update_last_notification_date_and_save( pub async fn update_last_notification_date_and_save(&mut self, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
&mut self,
date: &NaiveDateTime,
conn: &mut DbConn,
) -> EmptyResult {
self.last_notification_at = Some(date.to_owned()); self.last_notification_at = Some(date.to_owned());
self.updated_at = date.to_owned(); self.updated_at = date.to_owned();
db_run! {conn: { db_run! {conn: {
crate::util::retry(|| { crate::util::retry(|| {
diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid))) diesel::update(schema::emergency_access::table.filter(schema::emergency_access::uuid.eq(&self.uuid)))
.set((emergency_access::last_notification_at.eq(date), emergency_access::updated_at.eq(date))) .set((schema::emergency_access::last_notification_at.eq(date), schema::emergency_access::updated_at.eq(date)))
.execute(conn) .execute(conn)
}, 10) }, 10)
.map_res("Error updating emergency access status") .map_res("Error updating emergency access status")
}} }}
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await { for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await {
ea.delete(conn).await?; ea.delete(conn).await?;
} }
@ -214,22 +208,22 @@ impl EmergencyAccess {
Ok(()) Ok(())
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&self.grantor_uuid, conn).await; User::update_uuid_revision(&self.grantor_uuid, conn).await;
db_run! { conn: { db_run! { conn: {
diesel::delete(emergency_access::table.filter(emergency_access::uuid.eq(self.uuid))) diesel::delete(schema::emergency_access::table.filter(schema::emergency_access::uuid.eq(self.uuid)))
.execute(conn) .execute(conn)
.map_res("Error removing user from emergency access") .map_res("Error removing user from emergency access")
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table schema::emergency_access::table
.filter(emergency_access::uuid.eq(uuid)) .filter(schema::emergency_access::uuid.eq(uuid))
.first::<EmergencyAccessDb>(conn) .first::<Self>(conn)
.ok().from_db() .ok()
}} }}
} }
@ -237,59 +231,59 @@ impl EmergencyAccess {
grantor_uuid: &str, grantor_uuid: &str,
grantee_uuid: &str, grantee_uuid: &str,
email: &str, email: &str,
conn: &mut DbConn, conn: &DbConn,
) -> Option<Self> { ) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table schema::emergency_access::table
.filter(emergency_access::grantor_uuid.eq(grantor_uuid)) .filter(schema::emergency_access::grantor_uuid.eq(grantor_uuid))
.filter(emergency_access::grantee_uuid.eq(grantee_uuid).or(emergency_access::email.eq(email))) .filter(schema::emergency_access::grantee_uuid.eq(grantee_uuid).or(schema::emergency_access::email.eq(email)))
.first::<EmergencyAccessDb>(conn) .first::<Self>(conn)
.ok().from_db() .ok()
}} }}
} }
pub async fn find_all_recoveries_initiated(conn: &mut DbConn) -> Vec<Self> { pub async fn find_all_recoveries_initiated(conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table schema::emergency_access::table
.filter(emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32)) .filter(schema::emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32))
.filter(emergency_access::recovery_initiated_at.is_not_null()) .filter(schema::emergency_access::recovery_initiated_at.is_not_null())
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db() .load::<Self>(conn).expect("Error loading emergency_access")
}} }}
} }
pub async fn find_by_uuid_and_grantor_uuid(uuid: &str, grantor_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_grantor_uuid(uuid: &str, grantor_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table schema::emergency_access::table
.filter(emergency_access::uuid.eq(uuid)) .filter(schema::emergency_access::uuid.eq(uuid))
.filter(emergency_access::grantor_uuid.eq(grantor_uuid)) .filter(schema::emergency_access::grantor_uuid.eq(grantor_uuid))
.first::<EmergencyAccessDb>(conn) .first::<Self>(conn)
.ok().from_db() .ok()
}} }}
} }
pub async fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table schema::emergency_access::table
.filter(emergency_access::grantee_uuid.eq(grantee_uuid)) .filter(schema::emergency_access::grantee_uuid.eq(grantee_uuid))
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db() .load::<Self>(conn).expect("Error loading emergency_access")
}} }}
} }
pub async fn find_invited_by_grantee_email(grantee_email: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_invited_by_grantee_email(grantee_email: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table schema::emergency_access::table
.filter(emergency_access::email.eq(grantee_email)) .filter(schema::emergency_access::email.eq(grantee_email))
.filter(emergency_access::status.eq(EmergencyAccessStatus::Invited as i32)) .filter(schema::emergency_access::status.eq(EmergencyAccessStatus::Invited as i32))
.first::<EmergencyAccessDb>(conn) .first::<Self>(conn)
.ok().from_db() .ok()
}} }}
} }
pub async fn find_all_by_grantor_uuid(grantor_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_all_by_grantor_uuid(grantor_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
emergency_access::table schema::emergency_access::table
.filter(emergency_access::grantor_uuid.eq(grantor_uuid)) .filter(schema::emergency_access::grantor_uuid.eq(grantor_uuid))
.load::<EmergencyAccessDb>(conn).expect("Error loading emergency_access").from_db() .load::<Self>(conn).expect("Error loading emergency_access")
}} }}
} }
} }

Datei anzeigen

@ -6,33 +6,32 @@ use crate::{api::EmptyResult, error::MapResult, CONFIG};
use chrono::{Duration, NaiveDateTime, Utc}; use chrono::{Duration, NaiveDateTime, Utc};
// https://bitwarden.com/help/event-logs/ // https://bitwarden.com/help/event-logs/
use crate::db::schema::event;
db_object! { // Upstream: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Services/Implementations/EventService.cs
// Upstream: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Services/Implementations/EventService.cs // Upstream: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Api/Models/Public/Response/EventResponseModel.cs
// Upstream: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Api/Models/Public/Response/EventResponseModel.cs // Upstream SQL: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Sql/dbo/Tables/Event.sql
// Upstream SQL: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Sql/dbo/Tables/Event.sql #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = event)]
#[diesel(table_name = event)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct Event {
pub struct Event { pub uuid: String,
pub uuid: String, pub event_type: i32, // EventType
pub event_type: i32, // EventType pub user_uuid: Option<String>,
pub user_uuid: Option<String>, pub org_uuid: Option<String>,
pub org_uuid: Option<String>, pub cipher_uuid: Option<String>,
pub cipher_uuid: Option<String>, pub collection_uuid: Option<String>,
pub collection_uuid: Option<String>, pub group_uuid: Option<String>,
pub group_uuid: Option<String>, pub org_user_uuid: Option<String>,
pub org_user_uuid: Option<String>, pub act_user_uuid: Option<String>,
pub act_user_uuid: Option<String>, // Upstream enum: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Enums/DeviceType.cs
// Upstream enum: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Enums/DeviceType.cs pub device_type: Option<i32>,
pub device_type: Option<i32>, pub ip_address: Option<String>,
pub ip_address: Option<String>, pub event_date: NaiveDateTime,
pub event_date: NaiveDateTime, pub policy_uuid: Option<String>,
pub policy_uuid: Option<String>, pub provider_uuid: Option<String>,
pub provider_uuid: Option<String>, pub provider_user_uuid: Option<String>,
pub provider_user_uuid: Option<String>, pub provider_org_uuid: Option<String>,
pub provider_org_uuid: Option<String>,
}
} }
// Upstream enum: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Enums/EventType.cs // Upstream enum: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Enums/EventType.cs
@ -178,27 +177,27 @@ impl Event {
/// ############# /// #############
/// Basic Queries /// Basic Queries
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
diesel::replace_into(event::table) diesel::replace_into(schema::event::table)
.values(EventDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
.map_res("Error saving event") .map_res("Error saving event")
} }
postgresql { postgresql {
diesel::insert_into(event::table) diesel::insert_into(schema::event::table)
.values(EventDb::to_db(self)) .values(self)
.on_conflict(event::uuid) .on_conflict(schema::event::uuid)
.do_update() .do_update()
.set(EventDb::to_db(self)) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving event") .map_res("Error saving event")
} }
} }
} }
pub async fn save_user_event(events: Vec<Event>, conn: &mut DbConn) -> EmptyResult { pub async fn save_user_event(events: Vec<Event>, conn: &DbConn) -> EmptyResult {
// Special save function which is able to handle multiple events. // Special save function which is able to handle multiple events.
// SQLite doesn't support the DEFAULT argument, and does not support inserting multiple values at the same time. // SQLite doesn't support the DEFAULT argument, and does not support inserting multiple values at the same time.
// MySQL and PostgreSQL do. // MySQL and PostgreSQL do.
@ -208,24 +207,22 @@ impl Event {
// We loop through the events here and insert them one at a time. // We loop through the events here and insert them one at a time.
sqlite { sqlite {
for event in events { for event in events {
diesel::insert_or_ignore_into(event::table) diesel::insert_or_ignore_into(schema::event::table)
.values(EventDb::to_db(&event)) .values(&event)
.execute(conn) .execute(conn)
.unwrap_or_default(); .unwrap_or_default();
} }
Ok(()) Ok(())
} }
mysql { mysql {
let events: Vec<EventDb> = events.iter().map(EventDb::to_db).collect(); diesel::insert_or_ignore_into(schema::event::table)
diesel::insert_or_ignore_into(event::table)
.values(&events) .values(&events)
.execute(conn) .execute(conn)
.unwrap_or_default(); .unwrap_or_default();
Ok(()) Ok(())
} }
postgresql { postgresql {
let events: Vec<EventDb> = events.iter().map(EventDb::to_db).collect(); diesel::insert_into(schema::event::table)
diesel::insert_into(event::table)
.values(&events) .values(&events)
.on_conflict_do_nothing() .on_conflict_do_nothing()
.execute(conn) .execute(conn)
@ -235,9 +232,9 @@ impl Event {
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(event::table.filter(event::uuid.eq(self.uuid))) diesel::delete(schema::event::table.filter(schema::event::uuid.eq(self.uuid)))
.execute(conn) .execute(conn)
.map_res("Error deleting event") .map_res("Error deleting event")
}} }}
@ -249,24 +246,23 @@ impl Event {
org_uuid: &str, org_uuid: &str,
start: &NaiveDateTime, start: &NaiveDateTime,
end: &NaiveDateTime, end: &NaiveDateTime,
conn: &mut DbConn, conn: &DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
event::table schema::event::table
.filter(event::org_uuid.eq(org_uuid)) .filter(schema::event::org_uuid.eq(org_uuid))
.filter(event::event_date.between(start, end)) .filter(schema::event::event_date.between(start, end))
.order_by(event::event_date.desc()) .order_by(schema::event::event_date.desc())
.limit(Self::PAGE_SIZE) .limit(Self::PAGE_SIZE)
.load::<EventDb>(conn) .load::<Self>(conn)
.expect("Error filtering events") .expect("Error filtering events")
.from_db()
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
event::table schema::event::table
.filter(event::org_uuid.eq(org_uuid)) .filter(schema::event::org_uuid.eq(org_uuid))
.count() .count()
.first::<i64>(conn) .first::<i64>(conn)
.ok() .ok()
@ -279,20 +275,19 @@ impl Event {
user_org_uuid: &str, user_org_uuid: &str,
start: &NaiveDateTime, start: &NaiveDateTime,
end: &NaiveDateTime, end: &NaiveDateTime,
conn: &mut DbConn, conn: &DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
event::table schema::event::table
.inner_join(users_organizations::table.on(users_organizations::uuid.eq(user_org_uuid))) .inner_join(users_organizations::table.on(users_organizations::uuid.eq(user_org_uuid)))
.filter(event::org_uuid.eq(org_uuid)) .filter(schema::event::org_uuid.eq(org_uuid))
.filter(event::event_date.between(start, end)) .filter(schema::event::event_date.between(start, end))
.filter(event::user_uuid.eq(users_organizations::user_uuid.nullable()).or(event::act_user_uuid.eq(users_organizations::user_uuid.nullable()))) .filter(schema::event::user_uuid.eq(users_organizations::user_uuid.nullable()).or(schema::event::act_user_uuid.eq(users_organizations::user_uuid.nullable())))
.select(event::all_columns) .select(schema::event::all_columns)
.order_by(event::event_date.desc()) .order_by(schema::event::event_date.desc())
.limit(Self::PAGE_SIZE) .limit(Self::PAGE_SIZE)
.load::<EventDb>(conn) .load::<Self>(conn)
.expect("Error filtering events") .expect("Error filtering events")
.from_db()
}} }}
} }
@ -300,25 +295,24 @@ impl Event {
cipher_uuid: &str, cipher_uuid: &str,
start: &NaiveDateTime, start: &NaiveDateTime,
end: &NaiveDateTime, end: &NaiveDateTime,
conn: &mut DbConn, conn: &DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
event::table schema::event::table
.filter(event::cipher_uuid.eq(cipher_uuid)) .filter(schema::event::cipher_uuid.eq(cipher_uuid))
.filter(event::event_date.between(start, end)) .filter(schema::event::event_date.between(start, end))
.order_by(event::event_date.desc()) .order_by(schema::event::event_date.desc())
.limit(Self::PAGE_SIZE) .limit(Self::PAGE_SIZE)
.load::<EventDb>(conn) .load::<Self>(conn)
.expect("Error filtering events") .expect("Error filtering events")
.from_db()
}} }}
} }
pub async fn clean_events(conn: &mut DbConn) -> EmptyResult { pub async fn clean_events(conn: &DbConn) -> EmptyResult {
if let Some(days_to_retain) = CONFIG.events_days_retain() { if let Some(days_to_retain) = CONFIG.events_days_retain() {
let dt = Utc::now().naive_utc() - Duration::days(days_to_retain); let dt = Utc::now().naive_utc() - Duration::days(days_to_retain);
db_run! { conn: { db_run! { conn: {
diesel::delete(event::table.filter(event::event_date.lt(dt))) diesel::delete(schema::event::table.filter(schema::event::event_date.lt(dt)))
.execute(conn) .execute(conn)
.map_res("Error cleaning old events") .map_res("Error cleaning old events")
}} }}

Datei anzeigen

@ -1,13 +1,11 @@
use super::User; use super::User;
use crate::db::schema::favorites;
db_object! { #[derive(Identifiable, Queryable, Insertable)]
#[derive(Identifiable, Queryable, Insertable)] #[diesel(table_name = favorites)]
#[diesel(table_name = favorites)] #[diesel(primary_key(user_uuid, cipher_uuid))]
#[diesel(primary_key(user_uuid, cipher_uuid))] pub struct Favorite {
pub struct Favorite { pub user_uuid: String,
pub user_uuid: String, pub cipher_uuid: String,
pub cipher_uuid: String,
}
} }
use crate::db::DbConn; use crate::db::DbConn;
@ -17,7 +15,7 @@ use crate::error::MapResult;
impl Favorite { impl Favorite {
// Returns whether the specified cipher is a favorite of the specified user. // Returns whether the specified cipher is a favorite of the specified user.
pub async fn is_favorite(cipher_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_favorite(cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> bool {
db_run! { conn: { db_run! { conn: {
let query = favorites::table let query = favorites::table
.filter(favorites::cipher_uuid.eq(cipher_uuid)) .filter(favorites::cipher_uuid.eq(cipher_uuid))
@ -29,7 +27,7 @@ impl Favorite {
} }
// Sets whether the specified cipher is a favorite of the specified user. // Sets whether the specified cipher is a favorite of the specified user.
pub async fn set_favorite(favorite: bool, cipher_uuid: &str, user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn set_favorite(favorite: bool, cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> EmptyResult {
let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn).await, favorite); let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn).await, favorite);
match (old, new) { match (old, new) {
(false, true) => { (false, true) => {
@ -62,18 +60,18 @@ impl Favorite {
} }
// Delete all favorite entries associated with the specified cipher. // Delete all favorite entries associated with the specified cipher.
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid))) diesel::delete(schema::favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid)))
.execute(conn) .execute(conn)
.map_res("Error removing favorites by cipher") .map_res("Error removing favorites by cipher")
}} }}
} }
// Delete all favorite entries associated with the specified user. // Delete all favorite entries associated with the specified user.
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(favorites::table.filter(favorites::user_uuid.eq(user_uuid))) diesel::delete(schema::favorites::table.filter(favorites::user_uuid.eq(user_uuid)))
.execute(conn) .execute(conn)
.map_res("Error removing favorites by user") .map_res("Error removing favorites by user")
}} }}
@ -81,9 +79,9 @@ impl Favorite {
/// Return a vec with (cipher_uuid) this will only contain favorite flagged ciphers /// Return a vec with (cipher_uuid) this will only contain favorite flagged ciphers
/// This is used during a full sync so we only need one query for all favorite cipher matches. /// This is used during a full sync so we only need one query for all favorite cipher matches.
pub async fn get_all_cipher_uuid_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<String> { pub async fn get_all_cipher_uuid_by_user(user_uuid: &str, conn: &DbConn) -> Vec<String> {
db_run! { conn: { db_run! { conn: {
favorites::table schema::favorites::table
.filter(favorites::user_uuid.eq(user_uuid)) .filter(favorites::user_uuid.eq(user_uuid))
.select(favorites::cipher_uuid) .select(favorites::cipher_uuid)
.load::<String>(conn) .load::<String>(conn)

Datei anzeigen

@ -2,26 +2,25 @@ use chrono::{NaiveDateTime, Utc};
use serde_json::Value; use serde_json::Value;
use super::User; use super::User;
use crate::db::schema::{folders, folders_ciphers};
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = folders)]
#[diesel(table_name = folders)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct Folder {
pub struct Folder { pub uuid: String,
pub uuid: String, pub created_at: NaiveDateTime,
pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime,
pub updated_at: NaiveDateTime, pub user_uuid: String,
pub user_uuid: String, pub name: String,
pub name: String, }
}
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
#[diesel(table_name = folders_ciphers)] #[diesel(table_name = folders_ciphers)]
#[diesel(primary_key(cipher_uuid, folder_uuid))] #[diesel(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
@ -67,14 +66,14 @@ use crate::error::MapResult;
/// Database methods /// Database methods
impl Folder { impl Folder {
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&self.user_uuid, conn).await; User::update_uuid_revision(&self.user_uuid, conn).await;
self.updated_at = Utc::now().naive_utc(); self.updated_at = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(folders::table) match diesel::replace_into(folders::table)
.values(FolderDb::to_db(self)) .values(&*self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -82,7 +81,7 @@ impl Folder {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(folders::table) diesel::update(folders::table)
.filter(folders::uuid.eq(&self.uuid)) .filter(folders::uuid.eq(&self.uuid))
.set(FolderDb::to_db(self)) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving folder") .map_res("Error saving folder")
} }
@ -90,19 +89,18 @@ impl Folder {
}.map_res("Error saving folder") }.map_res("Error saving folder")
} }
postgresql { postgresql {
let value = FolderDb::to_db(self);
diesel::insert_into(folders::table) diesel::insert_into(folders::table)
.values(&value) .values(&*self)
.on_conflict(folders::uuid) .on_conflict(folders::uuid)
.do_update() .do_update()
.set(&value) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving folder") .map_res("Error saving folder")
} }
} }
} }
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&self.user_uuid, conn).await; User::update_uuid_revision(&self.user_uuid, conn).await;
FolderCipher::delete_all_by_folder(&self.uuid, conn).await?; FolderCipher::delete_all_by_folder(&self.uuid, conn).await?;
@ -113,49 +111,47 @@ impl Folder {
}} }}
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
for folder in Self::find_by_user(user_uuid, conn).await { for folder in Self::find_by_user(user_uuid, conn).await {
folder.delete(conn).await?; folder.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
folders::table folders::table
.filter(folders::uuid.eq(uuid)) .filter(folders::uuid.eq(uuid))
.first::<FolderDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
folders::table folders::table
.filter(folders::user_uuid.eq(user_uuid)) .filter(folders::user_uuid.eq(user_uuid))
.load::<FolderDb>(conn) .load::<Self>(conn)
.expect("Error loading folders") .expect("Error loading folders")
.from_db()
}} }}
} }
} }
impl FolderCipher { impl FolderCipher {
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
// Not checking for ForeignKey Constraints here. // Not checking for ForeignKey Constraints here.
// Table folders_ciphers does not have ForeignKey Constraints which would cause conflicts. // Table folders_ciphers does not have ForeignKey Constraints which would cause conflicts.
// This table has no constraints pointing to itself, but only to others. // This table has no constraints pointing to itself, but only to others.
diesel::replace_into(folders_ciphers::table) diesel::replace_into(folders_ciphers::table)
.values(FolderCipherDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
.map_res("Error adding cipher to folder") .map_res("Error adding cipher to folder")
} }
postgresql { postgresql {
diesel::insert_into(folders_ciphers::table) diesel::insert_into(folders_ciphers::table)
.values(FolderCipherDb::to_db(self)) .values(self)
.on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid)) .on_conflict((folders_ciphers::cipher_uuid, folders_ciphers::folder_uuid))
.do_nothing() .do_nothing()
.execute(conn) .execute(conn)
@ -164,7 +160,7 @@ impl FolderCipher {
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete( diesel::delete(
folders_ciphers::table folders_ciphers::table
@ -176,7 +172,7 @@ impl FolderCipher {
}} }}
} }
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))) diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)))
.execute(conn) .execute(conn)
@ -184,7 +180,7 @@ impl FolderCipher {
}} }}
} }
pub async fn delete_all_by_folder(folder_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_folder(folder_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid))) diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid)))
.execute(conn) .execute(conn)
@ -192,30 +188,28 @@ impl FolderCipher {
}} }}
} }
pub async fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
folders_ciphers::table folders_ciphers::table
.filter(folders_ciphers::folder_uuid.eq(folder_uuid)) .filter(folders_ciphers::folder_uuid.eq(folder_uuid))
.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)) .filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))
.first::<FolderCipherDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_folder(folder_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
folders_ciphers::table folders_ciphers::table
.filter(folders_ciphers::folder_uuid.eq(folder_uuid)) .filter(folders_ciphers::folder_uuid.eq(folder_uuid))
.load::<FolderCipherDb>(conn) .load::<Self>(conn)
.expect("Error loading folders") .expect("Error loading folders")
.from_db()
}} }}
} }
/// Return a vec with (cipher_uuid, folder_uuid) /// Return a vec with (cipher_uuid, folder_uuid)
/// This is used during a full sync so we only need one query for all folder matches. /// This is used during a full sync so we only need one query for all folder matches.
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<(String, String)> { pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<(String, String)> {
db_run! { conn: { db_run! { conn: {
folders_ciphers::table folders_ciphers::table
.inner_join(folders::table) .inner_join(folders::table)

Datei anzeigen

@ -1,37 +1,36 @@
use crate::db::schema::{collections_groups, groups, groups_users};
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use serde_json::Value; use serde_json::Value;
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = groups)]
#[diesel(table_name = groups)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct Group {
pub struct Group { pub uuid: String,
pub uuid: String, pub organizations_uuid: String,
pub organizations_uuid: String, pub name: String,
pub name: String, pub access_all: bool,
pub access_all: bool, pub external_id: Option<String>,
pub external_id: Option<String>, pub creation_date: NaiveDateTime,
pub creation_date: NaiveDateTime, pub revision_date: NaiveDateTime,
pub revision_date: NaiveDateTime, }
}
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
#[diesel(table_name = collections_groups)] #[diesel(table_name = collections_groups)]
#[diesel(primary_key(collections_uuid, groups_uuid))] #[diesel(primary_key(collections_uuid, groups_uuid))]
pub struct CollectionGroup { pub struct CollectionGroup {
pub collections_uuid: String, pub collections_uuid: String,
pub groups_uuid: String, pub groups_uuid: String,
pub read_only: bool, pub read_only: bool,
pub hide_passwords: bool, pub hide_passwords: bool,
} }
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
#[diesel(table_name = groups_users)] #[diesel(table_name = groups_users)]
#[diesel(primary_key(groups_uuid, users_organizations_uuid))] #[diesel(primary_key(groups_uuid, users_organizations_uuid))]
pub struct GroupUser { pub struct GroupUser {
pub groups_uuid: String, pub groups_uuid: String,
pub users_organizations_uuid: String pub users_organizations_uuid: String,
}
} }
/// Local methods /// Local methods
@ -69,7 +68,7 @@ impl Group {
}) })
} }
pub async fn to_json_details(&self, conn: &mut DbConn) -> Value { pub async fn to_json_details(&self, conn: &DbConn) -> Value {
let collections_groups: Vec<Value> = CollectionGroup::find_by_group(&self.uuid, conn) let collections_groups: Vec<Value> = CollectionGroup::find_by_group(&self.uuid, conn)
.await .await
.iter() .iter()
@ -131,13 +130,13 @@ use super::{User, UserOrganization};
/// Database methods /// Database methods
impl Group { impl Group {
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
self.revision_date = Utc::now().naive_utc(); self.revision_date = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(groups::table) match diesel::replace_into(groups::table)
.values(GroupDb::to_db(self)) .values(&*self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -145,7 +144,7 @@ impl Group {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(groups::table) diesel::update(groups::table)
.filter(groups::uuid.eq(&self.uuid)) .filter(groups::uuid.eq(&self.uuid))
.set(GroupDb::to_db(self)) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving group") .map_res("Error saving group")
} }
@ -153,36 +152,34 @@ impl Group {
}.map_res("Error saving group") }.map_res("Error saving group")
} }
postgresql { postgresql {
let value = GroupDb::to_db(self);
diesel::insert_into(groups::table) diesel::insert_into(groups::table)
.values(&value) .values(&*self)
.on_conflict(groups::uuid) .on_conflict(groups::uuid)
.do_update() .do_update()
.set(&value) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving group") .map_res("Error saving group")
} }
} }
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
for group in Self::find_by_organization(org_uuid, conn).await { for group in Self::find_by_organization(org_uuid, conn).await {
group.delete(conn).await?; group.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn find_by_organization(organizations_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_organization(organizations_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.filter(groups::organizations_uuid.eq(organizations_uuid)) .filter(groups::organizations_uuid.eq(organizations_uuid))
.load::<GroupDb>(conn) .load::<Self>(conn)
.expect("Error loading groups") .expect("Error loading groups")
.from_db()
}} }}
} }
pub async fn count_by_org(organizations_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(organizations_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.filter(groups::organizations_uuid.eq(organizations_uuid)) .filter(groups::organizations_uuid.eq(organizations_uuid))
@ -193,27 +190,25 @@ impl Group {
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.filter(groups::uuid.eq(uuid)) .filter(groups::uuid.eq(uuid))
.first::<GroupDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_external_id(id: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_external_id(id: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.filter(groups::external_id.eq(id)) .filter(groups::external_id.eq(id))
.first::<GroupDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
//Returns all organizations the user has full access to //Returns all organizations the user has full access to
pub async fn gather_user_organizations_full_access(user_uuid: &str, conn: &mut DbConn) -> Vec<String> { pub async fn gather_user_organizations_full_access(user_uuid: &str, conn: &DbConn) -> Vec<String> {
db_run! { conn: { db_run! { conn: {
groups_users::table groups_users::table
.inner_join(users_organizations::table.on( .inner_join(users_organizations::table.on(
@ -231,7 +226,7 @@ impl Group {
}} }}
} }
pub async fn is_in_full_access_group(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_in_full_access_group(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> bool {
db_run! { conn: { db_run! { conn: {
groups::table groups::table
.inner_join(groups_users::table.on( .inner_join(groups_users::table.on(
@ -249,7 +244,7 @@ impl Group {
}} }}
} }
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
CollectionGroup::delete_all_by_group(&self.uuid, conn).await?; CollectionGroup::delete_all_by_group(&self.uuid, conn).await?;
GroupUser::delete_all_by_group(&self.uuid, conn).await?; GroupUser::delete_all_by_group(&self.uuid, conn).await?;
@ -260,13 +255,13 @@ impl Group {
}} }}
} }
pub async fn update_revision(uuid: &str, conn: &mut DbConn) { pub async fn update_revision(uuid: &str, conn: &DbConn) {
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await { if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
warn!("Failed to update revision for {}: {:#?}", uuid, e); warn!("Failed to update revision for {}: {:#?}", uuid, e);
} }
} }
async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult { async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
db_run! {conn: { db_run! {conn: {
crate::util::retry(|| { crate::util::retry(|| {
diesel::update(groups::table.filter(groups::uuid.eq(uuid))) diesel::update(groups::table.filter(groups::uuid.eq(uuid)))
@ -279,7 +274,7 @@ impl Group {
} }
impl CollectionGroup { impl CollectionGroup {
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await; let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await;
for group_user in group_users { for group_user in group_users {
group_user.update_user_revision(conn).await; group_user.update_user_revision(conn).await;
@ -334,17 +329,16 @@ impl CollectionGroup {
} }
} }
pub async fn find_by_group(group_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_group(group_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
collections_groups::table collections_groups::table
.filter(collections_groups::groups_uuid.eq(group_uuid)) .filter(collections_groups::groups_uuid.eq(group_uuid))
.load::<CollectionGroupDb>(conn) .load::<Self>(conn)
.expect("Error loading collection groups") .expect("Error loading collection groups")
.from_db()
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
collections_groups::table collections_groups::table
.inner_join(groups_users::table.on( .inner_join(groups_users::table.on(
@ -355,24 +349,22 @@ impl CollectionGroup {
)) ))
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.select(collections_groups::all_columns) .select(collections_groups::all_columns)
.load::<CollectionGroupDb>(conn) .load::<Self>(conn)
.expect("Error loading user collection groups") .expect("Error loading user collection groups")
.from_db()
}} }}
} }
pub async fn find_by_collection(collection_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
collections_groups::table collections_groups::table
.filter(collections_groups::collections_uuid.eq(collection_uuid)) .filter(collections_groups::collections_uuid.eq(collection_uuid))
.select(collections_groups::all_columns) .select(collections_groups::all_columns)
.load::<CollectionGroupDb>(conn) .load::<Self>(conn)
.expect("Error loading collection groups") .expect("Error loading collection groups")
.from_db()
}} }}
} }
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await; let group_users = GroupUser::find_by_group(&self.groups_uuid, conn).await;
for group_user in group_users { for group_user in group_users {
group_user.update_user_revision(conn).await; group_user.update_user_revision(conn).await;
@ -387,7 +379,7 @@ impl CollectionGroup {
}} }}
} }
pub async fn delete_all_by_group(group_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_group(group_uuid: &str, conn: &DbConn) -> EmptyResult {
let group_users = GroupUser::find_by_group(group_uuid, conn).await; let group_users = GroupUser::find_by_group(group_uuid, conn).await;
for group_user in group_users { for group_user in group_users {
group_user.update_user_revision(conn).await; group_user.update_user_revision(conn).await;
@ -401,7 +393,7 @@ impl CollectionGroup {
}} }}
} }
pub async fn delete_all_by_collection(collection_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
let collection_assigned_to_groups = CollectionGroup::find_by_collection(collection_uuid, conn).await; let collection_assigned_to_groups = CollectionGroup::find_by_collection(collection_uuid, conn).await;
for collection_assigned_to_group in collection_assigned_to_groups { for collection_assigned_to_group in collection_assigned_to_groups {
let group_users = GroupUser::find_by_group(&collection_assigned_to_group.groups_uuid, conn).await; let group_users = GroupUser::find_by_group(&collection_assigned_to_group.groups_uuid, conn).await;
@ -420,7 +412,7 @@ impl CollectionGroup {
} }
impl GroupUser { impl GroupUser {
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
self.update_user_revision(conn).await; self.update_user_revision(conn).await;
db_run! { conn: db_run! { conn:
@ -466,27 +458,25 @@ impl GroupUser {
} }
} }
pub async fn find_by_group(group_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_group(group_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
groups_users::table groups_users::table
.filter(groups_users::groups_uuid.eq(group_uuid)) .filter(groups_users::groups_uuid.eq(group_uuid))
.load::<GroupUserDb>(conn) .load::<Self>(conn)
.expect("Error loading group users") .expect("Error loading group users")
.from_db()
}} }}
} }
pub async fn find_by_user(users_organizations_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(users_organizations_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
groups_users::table groups_users::table
.filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid)) .filter(groups_users::users_organizations_uuid.eq(users_organizations_uuid))
.load::<GroupUserDb>(conn) .load::<Self>(conn)
.expect("Error loading groups for user") .expect("Error loading groups for user")
.from_db()
}} }}
} }
pub async fn update_user_revision(&self, conn: &mut DbConn) { pub async fn update_user_revision(&self, conn: &DbConn) {
match UserOrganization::find_by_uuid(&self.users_organizations_uuid, conn).await { match UserOrganization::find_by_uuid(&self.users_organizations_uuid, conn).await {
Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await, Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await,
None => warn!("User could not be found!"), None => warn!("User could not be found!"),
@ -496,7 +486,7 @@ impl GroupUser {
pub async fn delete_by_group_id_and_user_id( pub async fn delete_by_group_id_and_user_id(
group_uuid: &str, group_uuid: &str,
users_organizations_uuid: &str, users_organizations_uuid: &str,
conn: &mut DbConn, conn: &DbConn,
) -> EmptyResult { ) -> EmptyResult {
match UserOrganization::find_by_uuid(users_organizations_uuid, conn).await { match UserOrganization::find_by_uuid(users_organizations_uuid, conn).await {
Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await, Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await,
@ -512,7 +502,7 @@ impl GroupUser {
}} }}
} }
pub async fn delete_all_by_group(group_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_group(group_uuid: &str, conn: &DbConn) -> EmptyResult {
let group_users = GroupUser::find_by_group(group_uuid, conn).await; let group_users = GroupUser::find_by_group(group_uuid, conn).await;
for group_user in group_users { for group_user in group_users {
group_user.update_user_revision(conn).await; group_user.update_user_revision(conn).await;
@ -526,7 +516,7 @@ impl GroupUser {
}} }}
} }
pub async fn delete_all_by_user(users_organizations_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(users_organizations_uuid: &str, conn: &DbConn) -> EmptyResult {
match UserOrganization::find_by_uuid(users_organizations_uuid, conn).await { match UserOrganization::find_by_uuid(users_organizations_uuid, conn).await {
Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await, Some(user) => User::update_uuid_revision(&user.user_uuid, conn).await,
None => warn!("User could not be found!"), None => warn!("User could not be found!"),

Datei anzeigen

@ -6,19 +6,18 @@ use crate::db::DbConn;
use crate::error::MapResult; use crate::error::MapResult;
use crate::util::UpCase; use crate::util::UpCase;
use super::{TwoFactor, UserOrgStatus, UserOrgType, UserOrganization}; use super::{TwoFactor, UserOrgType, UserOrganization};
use crate::db::schema::org_policies;
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = org_policies)]
#[diesel(table_name = org_policies)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct OrgPolicy {
pub struct OrgPolicy { pub uuid: String,
pub uuid: String, pub org_uuid: String,
pub org_uuid: String, pub atype: i32,
pub atype: i32, pub enabled: bool,
pub enabled: bool, pub data: String,
pub data: String,
}
} }
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/PolicyType.cs // https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/PolicyType.cs
@ -90,11 +89,11 @@ impl OrgPolicy {
/// Database methods /// Database methods
impl OrgPolicy { impl OrgPolicy {
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(org_policies::table) match diesel::replace_into(org_policies::table)
.values(OrgPolicyDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -102,7 +101,7 @@ impl OrgPolicy {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(org_policies::table) diesel::update(org_policies::table)
.filter(org_policies::uuid.eq(&self.uuid)) .filter(org_policies::uuid.eq(&self.uuid))
.set(OrgPolicyDb::to_db(self)) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving org_policy") .map_res("Error saving org_policy")
} }
@ -110,7 +109,6 @@ impl OrgPolicy {
}.map_res("Error saving org_policy") }.map_res("Error saving org_policy")
} }
postgresql { postgresql {
let value = OrgPolicyDb::to_db(self);
// We need to make sure we're not going to violate the unique constraint on org_uuid and atype. // 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 // This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
// not support multiple constraints on ON CONFLICT clauses. // not support multiple constraints on ON CONFLICT clauses.
@ -123,17 +121,17 @@ impl OrgPolicy {
.map_res("Error deleting org_policy for insert")?; .map_res("Error deleting org_policy for insert")?;
diesel::insert_into(org_policies::table) diesel::insert_into(org_policies::table)
.values(&value) .values(self)
.on_conflict(org_policies::uuid) .on_conflict(org_policies::uuid)
.do_update() .do_update()
.set(&value) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving org_policy") .map_res("Error saving org_policy")
} }
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(org_policies::table.filter(org_policies::uuid.eq(self.uuid))) diesel::delete(org_policies::table.filter(org_policies::uuid.eq(self.uuid)))
.execute(conn) .execute(conn)
@ -141,27 +139,25 @@ impl OrgPolicy {
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
.filter(org_policies::uuid.eq(uuid)) .filter(org_policies::uuid.eq(uuid))
.first::<OrgPolicyDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
.filter(org_policies::org_uuid.eq(org_uuid)) .filter(org_policies::org_uuid.eq(org_uuid))
.load::<OrgPolicyDb>(conn) .load::<Self>(conn)
.expect("Error loading org_policy") .expect("Error loading org_policy")
.from_db()
}} }}
} }
pub async fn find_confirmed_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
.inner_join( .inner_join(
@ -173,24 +169,22 @@ impl OrgPolicy {
users_organizations::status.eq(UserOrgStatus::Confirmed as i32) users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
) )
.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 async fn find_by_org_and_type(org_uuid: &str, policy_type: OrgPolicyType, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_org_and_type(org_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
.filter(org_policies::org_uuid.eq(org_uuid)) .filter(org_policies::org_uuid.eq(org_uuid))
.filter(org_policies::atype.eq(policy_type as i32)) .filter(org_policies::atype.eq(policy_type as i32))
.first::<OrgPolicyDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid))) diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid)))
.execute(conn) .execute(conn)
@ -201,7 +195,7 @@ impl OrgPolicy {
pub async fn find_accepted_and_confirmed_by_user_and_active_policy( pub async fn find_accepted_and_confirmed_by_user_and_active_policy(
user_uuid: &str, user_uuid: &str,
policy_type: OrgPolicyType, policy_type: OrgPolicyType,
conn: &mut DbConn, conn: &DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
@ -219,16 +213,15 @@ impl OrgPolicy {
.filter(org_policies::atype.eq(policy_type as i32)) .filter(org_policies::atype.eq(policy_type as i32))
.filter(org_policies::enabled.eq(true)) .filter(org_policies::enabled.eq(true))
.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 async fn find_confirmed_by_user_and_active_policy( pub async fn find_confirmed_by_user_and_active_policy(
user_uuid: &str, user_uuid: &str,
policy_type: OrgPolicyType, policy_type: OrgPolicyType,
conn: &mut DbConn, conn: &DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
@ -243,9 +236,8 @@ impl OrgPolicy {
.filter(org_policies::atype.eq(policy_type as i32)) .filter(org_policies::atype.eq(policy_type as i32))
.filter(org_policies::enabled.eq(true)) .filter(org_policies::enabled.eq(true))
.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()
}} }}
} }
@ -256,7 +248,7 @@ impl OrgPolicy {
user_uuid: &str, user_uuid: &str,
policy_type: OrgPolicyType, policy_type: OrgPolicyType,
exclude_org_uuid: Option<&str>, exclude_org_uuid: Option<&str>,
conn: &mut DbConn, conn: &DbConn,
) -> bool { ) -> bool {
for policy in for policy in
OrgPolicy::find_accepted_and_confirmed_by_user_and_active_policy(user_uuid, policy_type, conn).await OrgPolicy::find_accepted_and_confirmed_by_user_and_active_policy(user_uuid, policy_type, conn).await
@ -279,7 +271,7 @@ impl OrgPolicy {
user_uuid: &str, user_uuid: &str,
org_uuid: &str, org_uuid: &str,
exclude_current_org: bool, exclude_current_org: bool,
conn: &mut DbConn, conn: &DbConn,
) -> OrgPolicyResult { ) -> OrgPolicyResult {
// Enforce TwoFactor/TwoStep login // Enforce TwoFactor/TwoStep login
if TwoFactor::find_by_user(user_uuid, conn).await.is_empty() { if TwoFactor::find_by_user(user_uuid, conn).await.is_empty() {
@ -305,7 +297,7 @@ impl OrgPolicy {
Ok(()) Ok(())
} }
pub async fn org_is_reset_password_auto_enroll(org_uuid: &str, conn: &mut DbConn) -> bool { pub async fn org_is_reset_password_auto_enroll(org_uuid: &str, conn: &DbConn) -> bool {
match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await { match OrgPolicy::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await {
Some(policy) => match serde_json::from_str::<UpCase<ResetPasswordDataModel>>(&policy.data) { Some(policy) => match serde_json::from_str::<UpCase<ResetPasswordDataModel>>(&policy.data) {
Ok(opts) => { Ok(opts) => {
@ -321,7 +313,7 @@ impl OrgPolicy {
/// Returns true if the user belongs to an org that has enabled the `DisableHideEmail` /// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
/// option of the `Send Options` policy, and the user is not an owner or admin of that org. /// option of the `Send Options` policy, and the user is not an owner or admin of that org.
pub async fn is_hide_email_disabled(user_uuid: &str, conn: &mut DbConn) -> bool { pub async fn is_hide_email_disabled(user_uuid: &str, conn: &DbConn) -> bool {
for policy in for policy in
OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await
{ {

Datei anzeigen

@ -4,46 +4,45 @@ use serde_json::Value;
use std::cmp::Ordering; use std::cmp::Ordering;
use super::{CollectionUser, Group, GroupUser, OrgPolicy, OrgPolicyType, TwoFactor, User}; use super::{CollectionUser, Group, GroupUser, OrgPolicy, OrgPolicyType, TwoFactor, User};
use crate::db::schema::{organization_api_key, organizations, users_organizations};
use crate::CONFIG; use crate::CONFIG;
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = organizations)]
#[diesel(table_name = organizations)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct Organization {
pub struct Organization { pub uuid: String,
pub uuid: String, pub name: String,
pub name: String, pub billing_email: String,
pub billing_email: String, pub private_key: Option<String>,
pub private_key: Option<String>, pub public_key: Option<String>,
pub public_key: Option<String>, }
}
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = users_organizations)] #[diesel(table_name = users_organizations)]
#[diesel(primary_key(uuid))] #[diesel(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 reset_password_key: Option<String>, pub reset_password_key: Option<String>,
pub external_id: Option<String>, pub external_id: Option<String>,
} }
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = organization_api_key)] #[diesel(table_name = organization_api_key)]
#[diesel(primary_key(uuid, org_uuid))] #[diesel(primary_key(uuid, org_uuid))]
pub struct OrganizationApiKey { pub struct OrganizationApiKey {
pub uuid: String, pub uuid: String,
pub org_uuid: String, pub org_uuid: String,
pub atype: i32, pub atype: i32,
pub api_key: String, pub api_key: String,
pub revision_date: NaiveDateTime, pub revision_date: NaiveDateTime,
}
} }
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/OrganizationUserStatusType.cs // https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/OrganizationUserStatusType.cs
@ -267,7 +266,7 @@ use crate::error::MapResult;
/// Database methods /// Database methods
impl Organization { impl Organization {
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
if !email_address::EmailAddress::is_valid(self.billing_email.trim()) { if !email_address::EmailAddress::is_valid(self.billing_email.trim()) {
err!(format!("BillingEmail {} is not a valid email address", self.billing_email.trim())) err!(format!("BillingEmail {} is not a valid email address", self.billing_email.trim()))
} }
@ -279,7 +278,7 @@ impl Organization {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(organizations::table) match diesel::replace_into(organizations::table)
.values(OrganizationDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -287,7 +286,7 @@ impl Organization {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(organizations::table) diesel::update(organizations::table)
.filter(organizations::uuid.eq(&self.uuid)) .filter(organizations::uuid.eq(&self.uuid))
.set(OrganizationDb::to_db(self)) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving organization") .map_res("Error saving organization")
} }
@ -296,19 +295,18 @@ impl Organization {
} }
postgresql { postgresql {
let value = OrganizationDb::to_db(self);
diesel::insert_into(organizations::table) diesel::insert_into(organizations::table)
.values(&value) .values(self)
.on_conflict(organizations::uuid) .on_conflict(organizations::uuid)
.do_update() .do_update()
.set(&value) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving organization") .map_res("Error saving organization")
} }
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
use super::{Cipher, Collection}; use super::{Cipher, Collection};
Cipher::delete_all_by_organization(&self.uuid, conn).await?; Cipher::delete_all_by_organization(&self.uuid, conn).await?;
@ -324,24 +322,24 @@ impl Organization {
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
organizations::table organizations::table
.filter(organizations::uuid.eq(uuid)) .filter(organizations::uuid.eq(uuid))
.first::<OrganizationDb>(conn) .first::<Self>(conn)
.ok().from_db() .ok()
}} }}
} }
pub async fn get_all(conn: &mut DbConn) -> Vec<Self> { pub async fn get_all(conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
organizations::table.load::<OrganizationDb>(conn).expect("Error loading organizations").from_db() organizations::table.load::<Self>(conn).expect("Error loading organizations")
}} }}
} }
} }
impl UserOrganization { impl UserOrganization {
pub async fn to_json(&self, conn: &mut DbConn) -> Value { pub async fn to_json(&self, conn: &DbConn) -> Value {
let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap(); let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap();
// https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/ProfileOrganizationResponseModel.cs // https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/ProfileOrganizationResponseModel.cs
@ -406,12 +404,7 @@ impl UserOrganization {
}) })
} }
pub async fn to_json_user_details( pub async fn to_json_user_details(&self, include_collections: bool, include_groups: bool, conn: &DbConn) -> Value {
&self,
include_collections: bool,
include_groups: bool,
conn: &mut DbConn,
) -> Value {
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap(); let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
// Because BitWarden want the status to be -1 for revoked users we need to catch that here. // Because BitWarden want the status to be -1 for revoked users we need to catch that here.
@ -475,7 +468,7 @@ impl UserOrganization {
}) })
} }
pub async fn to_json_details(&self, conn: &mut DbConn) -> Value { pub async fn to_json_details(&self, conn: &DbConn) -> Value {
let coll_uuids = if self.access_all { let coll_uuids = if self.access_all {
vec![] // If we have complete access, no need to fill the array vec![] // If we have complete access, no need to fill the array
} else { } else {
@ -513,13 +506,13 @@ impl UserOrganization {
"Object": "organizationUserDetails", "Object": "organizationUserDetails",
}) })
} }
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&self.user_uuid, conn).await; User::update_uuid_revision(&self.user_uuid, conn).await;
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(users_organizations::table) match diesel::replace_into(users_organizations::table)
.values(UserOrganizationDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -527,7 +520,7 @@ impl UserOrganization {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(users_organizations::table) diesel::update(users_organizations::table)
.filter(users_organizations::uuid.eq(&self.uuid)) .filter(users_organizations::uuid.eq(&self.uuid))
.set(UserOrganizationDb::to_db(self)) .set(self)
.execute(conn) .execute(conn)
.map_res("Error adding user to organization") .map_res("Error adding user to organization")
}, },
@ -535,19 +528,18 @@ impl UserOrganization {
}.map_res("Error adding user to organization") }.map_res("Error adding user to organization")
} }
postgresql { postgresql {
let value = UserOrganizationDb::to_db(self);
diesel::insert_into(users_organizations::table) diesel::insert_into(users_organizations::table)
.values(&value) .values(self)
.on_conflict(users_organizations::uuid) .on_conflict(users_organizations::uuid)
.do_update() .do_update()
.set(&value) .set(self)
.execute(conn) .execute(conn)
.map_res("Error adding user to organization") .map_res("Error adding user to organization")
} }
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
User::update_uuid_revision(&self.user_uuid, conn).await; User::update_uuid_revision(&self.user_uuid, conn).await;
CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?; CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?;
@ -560,21 +552,21 @@ impl UserOrganization {
}} }}
} }
pub async fn delete_all_by_organization(org_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
for user_org in Self::find_by_org(org_uuid, conn).await { for user_org in Self::find_by_org(org_uuid, conn).await {
user_org.delete(conn).await?; user_org.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
for user_org in Self::find_any_state_by_user(user_uuid, conn).await { for user_org in Self::find_any_state_by_user(user_uuid, conn).await {
user_org.delete(conn).await?; user_org.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn find_by_email_and_org(email: &str, org_id: &str, conn: &mut DbConn) -> Option<UserOrganization> { pub async fn find_by_email_and_org(email: &str, org_id: &str, conn: &DbConn) -> Option<UserOrganization> {
if let Some(user) = super::User::find_by_mail(email, conn).await { if let Some(user) = super::User::find_by_mail(email, conn).await {
if let Some(user_org) = UserOrganization::find_by_user_and_org(&user.uuid, org_id, conn).await { if let Some(user_org) = UserOrganization::find_by_user_and_org(&user.uuid, org_id, conn).await {
return Some(user_org); return Some(user_org);
@ -596,55 +588,55 @@ impl UserOrganization {
(self.access_all || self.atype >= UserOrgType::Admin) && self.has_status(UserOrgStatus::Confirmed) (self.access_all || self.atype >= UserOrgType::Admin) && self.has_status(UserOrgStatus::Confirmed)
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::uuid.eq(uuid)) .filter(users_organizations::uuid.eq(uuid))
.first::<UserOrganizationDb>(conn) .first::<Self>(conn)
.ok().from_db() .ok()
}} }}
} }
pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::uuid.eq(uuid)) .filter(users_organizations::uuid.eq(uuid))
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.first::<UserOrganizationDb>(conn) .first::<Self>(conn)
.ok().from_db() .ok()
}} }}
} }
pub async fn find_confirmed_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32)) .filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
.load::<UserOrganizationDb>(conn) .load::<Self>(conn)
.unwrap_or_default().from_db() .unwrap_or_default()
}} }}
} }
pub async fn find_invited_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_invited_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(UserOrgStatus::Invited as i32)) .filter(users_organizations::status.eq(UserOrgStatus::Invited as i32))
.load::<UserOrganizationDb>(conn) .load::<Self>(conn)
.unwrap_or_default().from_db() .unwrap_or_default()
}} }}
} }
pub async fn find_any_state_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_any_state_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.load::<UserOrganizationDb>(conn) .load::<Self>(conn)
.unwrap_or_default().from_db() .unwrap_or_default()
}} }}
} }
pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
@ -656,16 +648,16 @@ impl UserOrganization {
}} }}
} }
pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.load::<UserOrganizationDb>(conn) .load::<Self>(conn)
.expect("Error loading user organizations").from_db() .expect("Error loading user organizations")
}} }}
} }
pub async fn count_by_org(org_uuid: &str, conn: &mut DbConn) -> i64 { pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
@ -676,17 +668,17 @@ impl UserOrganization {
}} }}
} }
pub async fn find_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.filter(users_organizations::atype.eq(atype as i32)) .filter(users_organizations::atype.eq(atype as i32))
.load::<UserOrganizationDb>(conn) .load::<Self>(conn)
.expect("Error loading user organizations").from_db() .expect("Error loading user organizations")
}} }}
} }
pub async fn count_confirmed_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &mut DbConn) -> i64 { pub async fn count_confirmed_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &DbConn) -> i64 {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
@ -698,26 +690,26 @@ impl UserOrganization {
}} }}
} }
pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.first::<UserOrganizationDb>(conn) .first::<Self>(conn)
.ok().from_db() .ok()
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.load::<UserOrganizationDb>(conn) .load::<Self>(conn)
.expect("Error loading user organizations").from_db() .expect("Error loading user organizations")
}} }}
} }
pub async fn get_org_uuid_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<String> { pub async fn get_org_uuid_by_user(user_uuid: &str, conn: &DbConn) -> Vec<String> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
@ -727,7 +719,7 @@ impl UserOrganization {
}} }}
} }
pub async fn find_by_user_and_policy(user_uuid: &str, policy_type: OrgPolicyType, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user_and_policy(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.inner_join( .inner_join(
@ -741,12 +733,12 @@ impl UserOrganization {
users_organizations::status.eq(UserOrgStatus::Confirmed as i32) users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
) )
.select(users_organizations::all_columns) .select(users_organizations::all_columns)
.load::<UserOrganizationDb>(conn) .load::<Self>(conn)
.unwrap_or_default().from_db() .unwrap_or_default()
}} }}
} }
pub async fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
@ -765,11 +757,11 @@ impl UserOrganization {
) )
.select(users_organizations::all_columns) .select(users_organizations::all_columns)
.distinct() .distinct()
.load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db() .load::<Self>(conn).expect("Error loading user organizations")
}} }}
} }
pub async fn user_has_ge_admin_access_to_cipher(user_uuid: &str, cipher_uuid: &str, conn: &mut DbConn) -> bool { pub async fn user_has_ge_admin_access_to_cipher(user_uuid: &str, cipher_uuid: &str, conn: &DbConn) -> bool {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.inner_join(ciphers::table.on(ciphers::uuid.eq(cipher_uuid).and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())))) .inner_join(ciphers::table.on(ciphers::uuid.eq(cipher_uuid).and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))))
@ -781,7 +773,7 @@ impl UserOrganization {
}} }}
} }
pub async fn find_by_collection_and_org(collection_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_collection_and_org(collection_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
@ -794,18 +786,18 @@ impl UserOrganization {
) )
) )
.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")
}} }}
} }
pub async fn find_by_external_id_and_org(ext_id: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_external_id_and_org(ext_id: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
users_organizations::table users_organizations::table
.filter( .filter(
users_organizations::external_id.eq(ext_id) users_organizations::external_id.eq(ext_id)
.and(users_organizations::org_uuid.eq(org_uuid)) .and(users_organizations::org_uuid.eq(org_uuid))
) )
.first::<UserOrganizationDb>(conn).ok().from_db() .first::<Self>(conn).ok()
}} }}
} }
} }
@ -815,7 +807,7 @@ impl OrganizationApiKey {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(organization_api_key::table) match diesel::replace_into(organization_api_key::table)
.values(OrganizationApiKeyDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -823,7 +815,7 @@ impl OrganizationApiKey {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(organization_api_key::table) diesel::update(organization_api_key::table)
.filter(organization_api_key::uuid.eq(&self.uuid)) .filter(organization_api_key::uuid.eq(&self.uuid))
.set(OrganizationApiKeyDb::to_db(self)) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving organization") .map_res("Error saving organization")
} }
@ -832,12 +824,11 @@ impl OrganizationApiKey {
} }
postgresql { postgresql {
let value = OrganizationApiKeyDb::to_db(self);
diesel::insert_into(organization_api_key::table) diesel::insert_into(organization_api_key::table)
.values(&value) .values(self)
.on_conflict((organization_api_key::uuid, organization_api_key::org_uuid)) .on_conflict((organization_api_key::uuid, organization_api_key::org_uuid))
.do_update() .do_update()
.set(&value) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving organization") .map_res("Error saving organization")
} }
@ -848,8 +839,8 @@ impl OrganizationApiKey {
db_run! { conn: { db_run! { conn: {
organization_api_key::table organization_api_key::table
.filter(organization_api_key::org_uuid.eq(org_uuid)) .filter(organization_api_key::org_uuid.eq(org_uuid))
.first::<OrganizationApiKeyDb>(conn) .first::<Self>(conn)
.ok().from_db() .ok()
}} }}
} }
} }

Datei anzeigen

@ -2,40 +2,38 @@ use chrono::{NaiveDateTime, Utc};
use serde_json::Value; use serde_json::Value;
use super::User; use super::User;
use crate::db::schema::sends;
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = sends)]
#[diesel(table_name = sends)] #[diesel(treat_none_as_null = true)]
#[diesel(treat_none_as_null = true)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct Send {
pub struct Send { pub uuid: String,
pub uuid: String,
pub user_uuid: Option<String>, pub user_uuid: Option<String>,
pub organization_uuid: Option<String>, pub organization_uuid: Option<String>,
pub name: String,
pub notes: Option<String>,
pub name: String, pub atype: i32,
pub notes: Option<String>, pub data: String,
pub akey: String,
pub password_hash: Option<Vec<u8>>,
password_salt: Option<Vec<u8>>,
password_iter: Option<i32>,
pub atype: i32, pub max_access_count: Option<i32>,
pub data: String, pub access_count: i32,
pub akey: String,
pub password_hash: Option<Vec<u8>>,
password_salt: Option<Vec<u8>>,
password_iter: Option<i32>,
pub max_access_count: Option<i32>, pub creation_date: NaiveDateTime,
pub access_count: i32, pub revision_date: NaiveDateTime,
pub expiration_date: Option<NaiveDateTime>,
pub deletion_date: NaiveDateTime,
pub creation_date: NaiveDateTime, pub disabled: bool,
pub revision_date: NaiveDateTime, pub hide_email: Option<bool>,
pub expiration_date: Option<NaiveDateTime>,
pub deletion_date: NaiveDateTime,
pub disabled: bool,
pub hide_email: Option<bool>,
}
} }
#[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)] #[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)]
@ -101,7 +99,7 @@ impl Send {
} }
} }
pub async fn creator_identifier(&self, conn: &mut DbConn) -> Option<String> { pub async fn creator_identifier(&self, conn: &DbConn) -> Option<String> {
if let Some(hide_email) = self.hide_email { if let Some(hide_email) = self.hide_email {
if hide_email { if hide_email {
return None; return None;
@ -148,7 +146,7 @@ impl Send {
}) })
} }
pub async fn to_json_access(&self, conn: &mut DbConn) -> Value { pub async fn to_json_access(&self, conn: &DbConn) -> Value {
use crate::util::format_date; use crate::util::format_date;
let data: Value = serde_json::from_str(&self.data).unwrap_or_default(); let data: Value = serde_json::from_str(&self.data).unwrap_or_default();
@ -174,14 +172,14 @@ use crate::api::EmptyResult;
use crate::error::MapResult; use crate::error::MapResult;
impl Send { impl Send {
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&mut self, conn: &DbConn) -> EmptyResult {
self.update_users_revision(conn).await; self.update_users_revision(conn).await;
self.revision_date = Utc::now().naive_utc(); self.revision_date = Utc::now().naive_utc();
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(sends::table) match diesel::replace_into(sends::table)
.values(SendDb::to_db(self)) .values(&*self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -189,7 +187,7 @@ impl Send {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(sends::table) diesel::update(sends::table)
.filter(sends::uuid.eq(&self.uuid)) .filter(sends::uuid.eq(&self.uuid))
.set(SendDb::to_db(self)) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving send") .map_res("Error saving send")
} }
@ -197,19 +195,18 @@ impl Send {
}.map_res("Error saving send") }.map_res("Error saving send")
} }
postgresql { postgresql {
let value = SendDb::to_db(self);
diesel::insert_into(sends::table) diesel::insert_into(sends::table)
.values(&value) .values(&*self)
.on_conflict(sends::uuid) .on_conflict(sends::uuid)
.do_update() .do_update()
.set(&value) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving send") .map_res("Error saving send")
} }
} }
} }
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
self.update_users_revision(conn).await; self.update_users_revision(conn).await;
if self.atype == SendType::File as i32 { if self.atype == SendType::File as i32 {
@ -224,13 +221,13 @@ impl Send {
} }
/// Purge all sends that are past their deletion date. /// Purge all sends that are past their deletion date.
pub async fn purge(conn: &mut DbConn) { pub async fn purge(conn: &DbConn) {
for send in Self::find_by_past_deletion_date(conn).await { for send in Self::find_by_past_deletion_date(conn).await {
send.delete(conn).await.ok(); send.delete(conn).await.ok();
} }
} }
pub async fn update_users_revision(&self, conn: &mut DbConn) -> Vec<String> { pub async fn update_users_revision(&self, conn: &DbConn) -> Vec<String> {
let mut user_uuids = Vec::new(); let mut user_uuids = Vec::new();
match &self.user_uuid { match &self.user_uuid {
Some(user_uuid) => { Some(user_uuid) => {
@ -244,14 +241,14 @@ impl Send {
user_uuids user_uuids
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
for send in Self::find_by_user(user_uuid, conn).await { for send in Self::find_by_user(user_uuid, conn).await {
send.delete(conn).await?; send.delete(conn).await?;
} }
Ok(()) Ok(())
} }
pub async fn find_by_access_id(access_id: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_access_id(access_id: &str, conn: &DbConn) -> Option<Self> {
use data_encoding::BASE64URL_NOPAD; use data_encoding::BASE64URL_NOPAD;
use uuid::Uuid; use uuid::Uuid;
@ -268,38 +265,37 @@ impl Send {
Self::find_by_uuid(&uuid, conn).await Self::find_by_uuid(&uuid, conn).await
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
sends::table sends::table
.filter(sends::uuid.eq(uuid)) .filter(sends::uuid.eq(uuid))
.first::<SendDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
sends::table sends::table
.filter(sends::user_uuid.eq(user_uuid)) .filter(sends::user_uuid.eq(user_uuid))
.load::<SendDb>(conn).expect("Error loading sends").from_db() .load::<Self>(conn).expect("Error loading sends")
}} }}
} }
pub async fn find_by_org(org_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
sends::table sends::table
.filter(sends::organization_uuid.eq(org_uuid)) .filter(sends::organization_uuid.eq(org_uuid))
.load::<SendDb>(conn).expect("Error loading sends").from_db() .load::<Self>(conn).expect("Error loading sends")
}} }}
} }
pub async fn find_by_past_deletion_date(conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_past_deletion_date(conn: &DbConn) -> Vec<Self> {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
db_run! {conn: { db_run! {conn: {
sends::table sends::table
.filter(sends::deletion_date.lt(now)) .filter(sends::deletion_date.lt(now))
.load::<SendDb>(conn).expect("Error loading sends").from_db() .load::<Self>(conn).expect("Error loading sends")
}} }}
} }
} }

Datei anzeigen

@ -1,19 +1,18 @@
use serde_json::Value; use serde_json::Value;
use crate::db::schema::twofactor;
use crate::{api::EmptyResult, db::DbConn, error::MapResult}; use crate::{api::EmptyResult, db::DbConn, error::MapResult};
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = twofactor)]
#[diesel(table_name = twofactor)] #[diesel(primary_key(uuid))]
#[diesel(primary_key(uuid))] pub struct TwoFactor {
pub struct TwoFactor { pub uuid: String,
pub uuid: String, pub user_uuid: String,
pub user_uuid: String, pub atype: i32,
pub atype: i32, pub enabled: bool,
pub enabled: bool, pub data: String,
pub data: String, pub last_used: i32,
pub last_used: i32,
}
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -68,11 +67,11 @@ impl TwoFactor {
/// Database methods /// Database methods
impl TwoFactor { impl TwoFactor {
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(twofactor::table) match diesel::replace_into(twofactor::table)
.values(TwoFactorDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -80,7 +79,7 @@ impl TwoFactor {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(twofactor::table) diesel::update(twofactor::table)
.filter(twofactor::uuid.eq(&self.uuid)) .filter(twofactor::uuid.eq(&self.uuid))
.set(TwoFactorDb::to_db(self)) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving twofactor") .map_res("Error saving twofactor")
} }
@ -88,7 +87,6 @@ impl TwoFactor {
}.map_res("Error saving twofactor") }.map_res("Error saving twofactor")
} }
postgresql { 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. // 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 // This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
// not support multiple constraints on ON CONFLICT clauses. // not support multiple constraints on ON CONFLICT clauses.
@ -97,17 +95,17 @@ impl TwoFactor {
.map_res("Error deleting twofactor for insert")?; .map_res("Error deleting twofactor for insert")?;
diesel::insert_into(twofactor::table) diesel::insert_into(twofactor::table)
.values(&value) .values(self)
.on_conflict(twofactor::uuid) .on_conflict(twofactor::uuid)
.do_update() .do_update()
.set(&value) .set(self)
.execute(conn) .execute(conn)
.map_res("Error saving twofactor") .map_res("Error saving twofactor")
} }
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(twofactor::table.filter(twofactor::uuid.eq(self.uuid))) diesel::delete(twofactor::table.filter(twofactor::uuid.eq(self.uuid)))
.execute(conn) .execute(conn)
@ -115,29 +113,27 @@ impl TwoFactor {
}} }}
} }
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
twofactor::table twofactor::table
.filter(twofactor::user_uuid.eq(user_uuid)) .filter(twofactor::user_uuid.eq(user_uuid))
.filter(twofactor::atype.lt(1000)) // Filter implementation types .filter(twofactor::atype.lt(1000)) // Filter implementation types
.load::<TwoFactorDb>(conn) .load::<Self>(conn)
.expect("Error loading twofactor") .expect("Error loading twofactor")
.from_db()
}} }}
} }
pub async fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
twofactor::table twofactor::table
.filter(twofactor::user_uuid.eq(user_uuid)) .filter(twofactor::user_uuid.eq(user_uuid))
.filter(twofactor::atype.eq(atype)) .filter(twofactor::atype.eq(atype))
.first::<TwoFactorDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid))) diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
.execute(conn) .execute(conn)
@ -145,13 +141,12 @@ impl TwoFactor {
}} }}
} }
pub async fn migrate_u2f_to_webauthn(conn: &mut DbConn) -> EmptyResult { pub async fn migrate_u2f_to_webauthn(conn: &DbConn) -> EmptyResult {
let u2f_factors = db_run! { conn: { let u2f_factors = db_run! { conn: {
twofactor::table twofactor::table
.filter(twofactor::atype.eq(TwoFactorType::U2f as i32)) .filter(twofactor::atype.eq(TwoFactorType::U2f as i32))
.load::<TwoFactorDb>(conn) .load::<Self>(conn)
.expect("Error loading twofactor") .expect("Error loading twofactor")
.from_db()
}}; }};
use crate::api::core::two_factor::webauthn::U2FRegistration; use crate::api::core::two_factor::webauthn::U2FRegistration;

Datei anzeigen

@ -1,21 +1,20 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use crate::db::schema::twofactor_incomplete;
use crate::{api::EmptyResult, auth::ClientIp, db::DbConn, error::MapResult, CONFIG}; use crate::{api::EmptyResult, auth::ClientIp, db::DbConn, error::MapResult, CONFIG};
db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[diesel(table_name = twofactor_incomplete)]
#[diesel(table_name = twofactor_incomplete)] #[diesel(primary_key(user_uuid, device_uuid))]
#[diesel(primary_key(user_uuid, device_uuid))] pub struct TwoFactorIncomplete {
pub struct TwoFactorIncomplete { pub user_uuid: String,
pub user_uuid: String, // This device UUID is simply what's claimed by the device. It doesn't
// This device UUID is simply what's claimed by the device. It doesn't // necessarily correspond to any UUID in the devices table, since a device
// necessarily correspond to any UUID in the devices table, since a device // must complete 2FA login before being added into the devices table.
// must complete 2FA login before being added into the devices table. pub device_uuid: String,
pub device_uuid: String, pub device_name: String,
pub device_name: String, pub login_time: NaiveDateTime,
pub login_time: NaiveDateTime, pub ip_address: String,
pub ip_address: String,
}
} }
impl TwoFactorIncomplete { impl TwoFactorIncomplete {
@ -24,7 +23,7 @@ impl TwoFactorIncomplete {
device_uuid: &str, device_uuid: &str,
device_name: &str, device_name: &str,
ip: &ClientIp, ip: &ClientIp,
conn: &mut DbConn, conn: &DbConn,
) -> EmptyResult { ) -> EmptyResult {
if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() { if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() {
return Ok(()); return Ok(());
@ -52,7 +51,7 @@ impl TwoFactorIncomplete {
}} }}
} }
pub async fn mark_complete(user_uuid: &str, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn mark_complete(user_uuid: &str, device_uuid: &str, conn: &DbConn) -> EmptyResult {
if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() { if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() {
return Ok(()); return Ok(());
} }
@ -60,32 +59,30 @@ impl TwoFactorIncomplete {
Self::delete_by_user_and_device(user_uuid, device_uuid, conn).await Self::delete_by_user_and_device(user_uuid, device_uuid, conn).await
} }
pub async fn find_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
twofactor_incomplete::table twofactor_incomplete::table
.filter(twofactor_incomplete::user_uuid.eq(user_uuid)) .filter(twofactor_incomplete::user_uuid.eq(user_uuid))
.filter(twofactor_incomplete::device_uuid.eq(device_uuid)) .filter(twofactor_incomplete::device_uuid.eq(device_uuid))
.first::<TwoFactorIncompleteDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_logins_before(dt: &NaiveDateTime, conn: &mut DbConn) -> Vec<Self> { pub async fn find_logins_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
twofactor_incomplete::table twofactor_incomplete::table
.filter(twofactor_incomplete::login_time.lt(dt)) .filter(twofactor_incomplete::login_time.lt(dt))
.load::<TwoFactorIncompleteDb>(conn) .load::<Self>(conn)
.expect("Error loading twofactor_incomplete") .expect("Error loading twofactor_incomplete")
.from_db()
}} }}
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
Self::delete_by_user_and_device(&self.user_uuid, &self.device_uuid, conn).await Self::delete_by_user_and_device(&self.user_uuid, &self.device_uuid, conn).await
} }
pub async fn delete_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(twofactor_incomplete::table diesel::delete(twofactor_incomplete::table
.filter(twofactor_incomplete::user_uuid.eq(user_uuid)) .filter(twofactor_incomplete::user_uuid.eq(user_uuid))
@ -95,7 +92,7 @@ impl TwoFactorIncomplete {
}} }}
} }
pub async fn delete_all_by_user(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(twofactor_incomplete::table.filter(twofactor_incomplete::user_uuid.eq(user_uuid))) diesel::delete(twofactor_incomplete::table.filter(twofactor_incomplete::user_uuid.eq(user_uuid)))
.execute(conn) .execute(conn)

Datei anzeigen

@ -4,62 +4,62 @@ use serde_json::Value;
use crate::crypto; use crate::crypto;
use crate::CONFIG; use crate::CONFIG;
db_object! { use crate::db::schema::{invitations, users};
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = users)]
#[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))]
pub struct User {
pub uuid: String,
pub enabled: bool,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub verified_at: Option<NaiveDateTime>,
pub last_verifying_at: Option<NaiveDateTime>,
pub login_verify_count: i32,
pub email: String, #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
pub email_new: Option<String>, #[diesel(table_name = users)]
pub email_new_token: Option<String>, #[diesel(treat_none_as_null = true)]
pub name: String, #[diesel(primary_key(uuid))]
pub struct User {
pub uuid: String,
pub enabled: bool,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub verified_at: Option<NaiveDateTime>,
pub last_verifying_at: Option<NaiveDateTime>,
pub login_verify_count: i32,
pub password_hash: Vec<u8>, pub email: String,
pub salt: Vec<u8>, pub email_new: Option<String>,
pub password_iterations: i32, pub email_new_token: Option<String>,
pub password_hint: Option<String>, pub name: String,
pub akey: String, pub password_hash: Vec<u8>,
pub private_key: Option<String>, pub salt: Vec<u8>,
pub public_key: Option<String>, pub password_iterations: i32,
pub password_hint: Option<String>,
#[diesel(column_name = "totp_secret")] // Note, this is only added to the UserDb structs, not to User pub akey: String,
_totp_secret: Option<String>, pub private_key: Option<String>,
pub totp_recover: Option<String>, pub public_key: Option<String>,
pub security_stamp: String, #[diesel(column_name = "totp_secret")] // Note, this is only added to the UserDb structs, not to User
pub stamp_exception: Option<String>, _totp_secret: Option<String>,
pub totp_recover: Option<String>,
pub equivalent_domains: String, pub security_stamp: String,
pub excluded_globals: String, pub stamp_exception: Option<String>,
pub client_kdf_type: i32, pub equivalent_domains: String,
pub client_kdf_iter: i32, pub excluded_globals: String,
pub client_kdf_memory: Option<i32>,
pub client_kdf_parallelism: Option<i32>,
pub api_key: Option<String>, pub client_kdf_type: i32,
pub client_kdf_iter: i32,
pub client_kdf_memory: Option<i32>,
pub client_kdf_parallelism: Option<i32>,
pub avatar_color: Option<String>, pub api_key: Option<String>,
pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore. pub avatar_color: Option<String>,
}
#[derive(Identifiable, Queryable, Insertable)] pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore.
#[diesel(table_name = invitations)] }
#[diesel(primary_key(email))]
pub struct Invitation { #[derive(Identifiable, Queryable, Insertable)]
pub email: String, #[diesel(table_name = invitations)]
} #[diesel(primary_key(email))]
pub struct Invitation {
pub email: String,
} }
pub enum UserKdfType { pub enum UserKdfType {
@ -224,7 +224,7 @@ use crate::error::MapResult;
/// Database methods /// Database methods
impl User { impl User {
pub async fn to_json(&self, conn: &mut DbConn) -> Value { pub async fn to_json(&self, conn: &DbConn) -> Value {
let mut orgs_json = Vec::new(); let mut orgs_json = Vec::new();
for c in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await { for c in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await {
orgs_json.push(c.to_json(conn).await); orgs_json.push(c.to_json(conn).await);
@ -261,7 +261,7 @@ impl User {
}) })
} }
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult { pub async 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")
} }
@ -271,7 +271,7 @@ impl User {
db_run! {conn: db_run! {conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(users::table) match diesel::replace_into(users::table)
.values(UserDb::to_db(self)) .values(&*self)
.execute(conn) .execute(conn)
{ {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -279,7 +279,7 @@ impl User {
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
diesel::update(users::table) diesel::update(users::table)
.filter(users::uuid.eq(&self.uuid)) .filter(users::uuid.eq(&self.uuid))
.set(UserDb::to_db(self)) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving user") .map_res("Error saving user")
} }
@ -287,19 +287,18 @@ impl User {
}.map_res("Error saving user") }.map_res("Error saving user")
} }
postgresql { postgresql {
let value = UserDb::to_db(self);
diesel::insert_into(users::table) // Insert or update diesel::insert_into(users::table) // Insert or update
.values(&value) .values(&*self)
.on_conflict(users::uuid) .on_conflict(users::uuid)
.do_update() .do_update()
.set(&value) .set(&*self)
.execute(conn) .execute(conn)
.map_res("Error saving user") .map_res("Error saving user")
} }
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await { for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await {
if user_org.atype == UserOrgType::Owner if user_org.atype == UserOrgType::Owner
&& UserOrganization::count_confirmed_by_org_and_type(&user_org.org_uuid, UserOrgType::Owner, conn).await && UserOrganization::count_confirmed_by_org_and_type(&user_org.org_uuid, UserOrgType::Owner, conn).await
@ -327,13 +326,13 @@ impl User {
}} }}
} }
pub async fn update_uuid_revision(uuid: &str, conn: &mut DbConn) { pub async fn update_uuid_revision(uuid: &str, conn: &DbConn) {
if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await { if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await {
warn!("Failed to update revision for {}: {:#?}", uuid, e); warn!("Failed to update revision for {}: {:#?}", uuid, e);
} }
} }
pub async fn update_all_revisions(conn: &mut DbConn) -> EmptyResult { pub async fn update_all_revisions(conn: &DbConn) -> EmptyResult {
let updated_at = Utc::now().naive_utc(); let updated_at = Utc::now().naive_utc();
db_run! {conn: { db_run! {conn: {
@ -346,13 +345,13 @@ impl User {
}} }}
} }
pub async fn update_revision(&mut self, conn: &mut DbConn) -> EmptyResult { pub async fn update_revision(&mut self, conn: &DbConn) -> EmptyResult {
self.updated_at = Utc::now().naive_utc(); self.updated_at = Utc::now().naive_utc();
Self::_update_revision(&self.uuid, &self.updated_at, conn).await Self::_update_revision(&self.uuid, &self.updated_at, conn).await
} }
async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult { async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {
db_run! {conn: { db_run! {conn: {
crate::util::retry(|| { crate::util::retry(|| {
diesel::update(users::table.filter(users::uuid.eq(uuid))) diesel::update(users::table.filter(users::uuid.eq(uuid)))
@ -363,30 +362,30 @@ impl User {
}} }}
} }
pub async fn find_by_mail(mail: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
let lower_mail = mail.to_lowercase(); let lower_mail = mail.to_lowercase();
db_run! {conn: { db_run! {conn: {
users::table users::table
.filter(users::email.eq(lower_mail)) .filter(users::email.eq(lower_mail))
.first::<UserDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
users::table.filter(users::uuid.eq(uuid)).first::<UserDb>(conn).ok().from_db() users::table.filter(users::uuid.eq(uuid)).first::<Self>(conn).ok()
}} }}
} }
pub async fn get_all(conn: &mut DbConn) -> Vec<Self> { pub async fn get_all(conn: &DbConn) -> Vec<Self> {
db_run! {conn: { db_run! {conn: {
users::table.load::<UserDb>(conn).expect("Error loading users").from_db() users::table.load::<Self>(conn).expect("Error loading users")
}} }}
} }
pub async fn last_active(&self, conn: &mut DbConn) -> Option<NaiveDateTime> { pub async fn last_active(&self, conn: &DbConn) -> Option<NaiveDateTime> {
match Device::find_latest_active_by_user(&self.uuid, conn).await { match Device::find_latest_active_by_user(&self.uuid, conn).await {
Some(device) => Some(device.updated_at), Some(device) => Some(device.updated_at),
None => None, None => None,
@ -402,7 +401,7 @@ impl Invitation {
} }
} }
pub async fn save(&self, conn: &mut DbConn) -> EmptyResult { pub async 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")
} }
@ -412,13 +411,13 @@ impl Invitation {
// Not checking for ForeignKey Constraints here // Not checking for ForeignKey Constraints here
// Table invitations does not have any ForeignKey Constraints. // Table invitations does not have any ForeignKey Constraints.
diesel::replace_into(invitations::table) diesel::replace_into(invitations::table)
.values(InvitationDb::to_db(self)) .values(self)
.execute(conn) .execute(conn)
.map_res("Error saving invitation") .map_res("Error saving invitation")
} }
postgresql { postgresql {
diesel::insert_into(invitations::table) diesel::insert_into(invitations::table)
.values(InvitationDb::to_db(self)) .values(self)
.on_conflict(invitations::email) .on_conflict(invitations::email)
.do_nothing() .do_nothing()
.execute(conn) .execute(conn)
@ -427,7 +426,7 @@ impl Invitation {
} }
} }
pub async fn delete(self, conn: &mut DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
db_run! {conn: { db_run! {conn: {
diesel::delete(invitations::table.filter(invitations::email.eq(self.email))) diesel::delete(invitations::table.filter(invitations::email.eq(self.email)))
.execute(conn) .execute(conn)
@ -435,18 +434,18 @@ impl Invitation {
}} }}
} }
pub async fn find_by_mail(mail: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> {
let lower_mail = mail.to_lowercase(); let lower_mail = mail.to_lowercase();
db_run! {conn: { db_run! {conn: {
invitations::table invitations::table
.filter(invitations::email.eq(lower_mail)) .filter(invitations::email.eq(lower_mail))
.first::<InvitationDb>(conn) .first::<Self>(conn)
.ok() .ok()
.from_db()
}} }}
} }
pub async fn take(mail: &str, conn: &mut DbConn) -> bool { pub async fn take(mail: &str, conn: &DbConn) -> bool {
match Self::find_by_mail(mail, conn).await { match Self::find_by_mail(mail, conn).await {
Some(invitation) => invitation.delete(conn).await.is_ok(), Some(invitation) => invitation.delete(conn).await.is_ok(),
None => false, None => false,

Datei anzeigen

@ -1,362 +0,0 @@
table! {
attachments (id) {
id -> Text,
cipher_uuid -> Text,
file_name -> Text,
file_size -> Integer,
akey -> Nullable<Text>,
}
}
table! {
ciphers (uuid) {
uuid -> Text,
created_at -> Datetime,
updated_at -> Datetime,
user_uuid -> Nullable<Text>,
organization_uuid -> Nullable<Text>,
atype -> Integer,
name -> Text,
notes -> Nullable<Text>,
fields -> Nullable<Text>,
data -> Text,
password_history -> Nullable<Text>,
deleted_at -> Nullable<Datetime>,
reprompt -> Nullable<Integer>,
}
}
table! {
ciphers_collections (cipher_uuid, collection_uuid) {
cipher_uuid -> Text,
collection_uuid -> Text,
}
}
table! {
collections (uuid) {
uuid -> Text,
org_uuid -> Text,
name -> Text,
external_id -> Nullable<Text>,
}
}
table! {
devices (uuid, user_uuid) {
uuid -> Text,
created_at -> Datetime,
updated_at -> Datetime,
user_uuid -> Text,
name -> Text,
atype -> Integer,
push_uuid -> Nullable<Text>,
push_token -> Nullable<Text>,
refresh_token -> Text,
twofactor_remember -> Nullable<Text>,
}
}
table! {
event (uuid) {
uuid -> Varchar,
event_type -> Integer,
user_uuid -> Nullable<Varchar>,
org_uuid -> Nullable<Varchar>,
cipher_uuid -> Nullable<Varchar>,
collection_uuid -> Nullable<Varchar>,
group_uuid -> Nullable<Varchar>,
org_user_uuid -> Nullable<Varchar>,
act_user_uuid -> Nullable<Varchar>,
device_type -> Nullable<Integer>,
ip_address -> Nullable<Text>,
event_date -> Timestamp,
policy_uuid -> Nullable<Varchar>,
provider_uuid -> Nullable<Varchar>,
provider_user_uuid -> Nullable<Varchar>,
provider_org_uuid -> Nullable<Varchar>,
}
}
table! {
favorites (user_uuid, cipher_uuid) {
user_uuid -> Text,
cipher_uuid -> Text,
}
}
table! {
folders (uuid) {
uuid -> Text,
created_at -> Datetime,
updated_at -> Datetime,
user_uuid -> Text,
name -> Text,
}
}
table! {
folders_ciphers (cipher_uuid, folder_uuid) {
cipher_uuid -> Text,
folder_uuid -> Text,
}
}
table! {
invitations (email) {
email -> Text,
}
}
table! {
org_policies (uuid) {
uuid -> Text,
org_uuid -> Text,
atype -> Integer,
enabled -> Bool,
data -> Text,
}
}
table! {
organizations (uuid) {
uuid -> Text,
name -> Text,
billing_email -> Text,
private_key -> Nullable<Text>,
public_key -> Nullable<Text>,
}
}
table! {
sends (uuid) {
uuid -> Text,
user_uuid -> Nullable<Text>,
organization_uuid -> Nullable<Text>,
name -> Text,
notes -> Nullable<Text>,
atype -> Integer,
data -> Text,
akey -> Text,
password_hash -> Nullable<Binary>,
password_salt -> Nullable<Binary>,
password_iter -> Nullable<Integer>,
max_access_count -> Nullable<Integer>,
access_count -> Integer,
creation_date -> Datetime,
revision_date -> Datetime,
expiration_date -> Nullable<Datetime>,
deletion_date -> Datetime,
disabled -> Bool,
hide_email -> Nullable<Bool>,
}
}
table! {
twofactor (uuid) {
uuid -> Text,
user_uuid -> Text,
atype -> Integer,
enabled -> Bool,
data -> Text,
last_used -> Integer,
}
}
table! {
twofactor_incomplete (user_uuid, device_uuid) {
user_uuid -> Text,
device_uuid -> Text,
device_name -> Text,
login_time -> Timestamp,
ip_address -> Text,
}
}
table! {
users (uuid) {
uuid -> Text,
enabled -> Bool,
created_at -> Datetime,
updated_at -> Datetime,
verified_at -> Nullable<Datetime>,
last_verifying_at -> Nullable<Datetime>,
login_verify_count -> Integer,
email -> Text,
email_new -> Nullable<Text>,
email_new_token -> Nullable<Text>,
name -> Text,
password_hash -> Binary,
salt -> Binary,
password_iterations -> Integer,
password_hint -> Nullable<Text>,
akey -> Text,
private_key -> Nullable<Text>,
public_key -> Nullable<Text>,
totp_secret -> Nullable<Text>,
totp_recover -> Nullable<Text>,
security_stamp -> Text,
stamp_exception -> Nullable<Text>,
equivalent_domains -> Text,
excluded_globals -> Text,
client_kdf_type -> Integer,
client_kdf_iter -> Integer,
client_kdf_memory -> Nullable<Integer>,
client_kdf_parallelism -> Nullable<Integer>,
api_key -> Nullable<Text>,
avatar_color -> Nullable<Text>,
external_id -> Nullable<Text>,
}
}
table! {
users_collections (user_uuid, collection_uuid) {
user_uuid -> Text,
collection_uuid -> Text,
read_only -> Bool,
hide_passwords -> Bool,
}
}
table! {
users_organizations (uuid) {
uuid -> Text,
user_uuid -> Text,
org_uuid -> Text,
access_all -> Bool,
akey -> Text,
status -> Integer,
atype -> Integer,
reset_password_key -> Nullable<Text>,
external_id -> Nullable<Text>,
}
}
table! {
organization_api_key (uuid, org_uuid) {
uuid -> Text,
org_uuid -> Text,
atype -> Integer,
api_key -> Text,
revision_date -> Timestamp,
}
}
table! {
emergency_access (uuid) {
uuid -> Text,
grantor_uuid -> Text,
grantee_uuid -> Nullable<Text>,
email -> Nullable<Text>,
key_encrypted -> Nullable<Text>,
atype -> Integer,
status -> Integer,
wait_time_days -> Integer,
recovery_initiated_at -> Nullable<Timestamp>,
last_notification_at -> Nullable<Timestamp>,
updated_at -> Timestamp,
created_at -> Timestamp,
}
}
table! {
groups (uuid) {
uuid -> Text,
organizations_uuid -> Text,
name -> Text,
access_all -> Bool,
external_id -> Nullable<Text>,
creation_date -> Timestamp,
revision_date -> Timestamp,
}
}
table! {
groups_users (groups_uuid, users_organizations_uuid) {
groups_uuid -> Text,
users_organizations_uuid -> Text,
}
}
table! {
collections_groups (collections_uuid, groups_uuid) {
collections_uuid -> Text,
groups_uuid -> Text,
read_only -> Bool,
hide_passwords -> Bool,
}
}
table! {
auth_requests (uuid) {
uuid -> Text,
user_uuid -> Text,
organization_uuid -> Nullable<Text>,
request_device_identifier -> Text,
device_type -> Integer,
request_ip -> Text,
response_device_id -> Nullable<Text>,
access_code -> Text,
public_key -> Text,
enc_key -> Nullable<Text>,
master_password_hash -> Nullable<Text>,
approved -> Nullable<Bool>,
creation_date -> Timestamp,
response_date -> Nullable<Timestamp>,
authentication_date -> Nullable<Timestamp>,
}
}
joinable!(attachments -> ciphers (cipher_uuid));
joinable!(ciphers -> organizations (organization_uuid));
joinable!(ciphers -> users (user_uuid));
joinable!(ciphers_collections -> ciphers (cipher_uuid));
joinable!(ciphers_collections -> collections (collection_uuid));
joinable!(collections -> organizations (org_uuid));
joinable!(devices -> users (user_uuid));
joinable!(folders -> users (user_uuid));
joinable!(folders_ciphers -> ciphers (cipher_uuid));
joinable!(folders_ciphers -> folders (folder_uuid));
joinable!(org_policies -> organizations (org_uuid));
joinable!(sends -> organizations (organization_uuid));
joinable!(sends -> users (user_uuid));
joinable!(twofactor -> users (user_uuid));
joinable!(users_collections -> collections (collection_uuid));
joinable!(users_collections -> users (user_uuid));
joinable!(users_organizations -> organizations (org_uuid));
joinable!(users_organizations -> users (user_uuid));
joinable!(users_organizations -> ciphers (org_uuid));
joinable!(organization_api_key -> organizations (org_uuid));
joinable!(emergency_access -> users (grantor_uuid));
joinable!(groups -> organizations (organizations_uuid));
joinable!(groups_users -> users_organizations (users_organizations_uuid));
joinable!(groups_users -> groups (groups_uuid));
joinable!(collections_groups -> collections (collections_uuid));
joinable!(collections_groups -> groups (groups_uuid));
joinable!(event -> users_organizations (uuid));
joinable!(auth_requests -> users (user_uuid));
allow_tables_to_appear_in_same_query!(
attachments,
ciphers,
ciphers_collections,
collections,
devices,
folders,
folders_ciphers,
invitations,
org_policies,
organizations,
sends,
twofactor,
users,
users_collections,
users_organizations,
organization_api_key,
emergency_access,
groups,
groups_users,
collections_groups,
event,
auth_requests,
);

Datei anzeigen

@ -1,362 +0,0 @@
table! {
attachments (id) {
id -> Text,
cipher_uuid -> Text,
file_name -> Text,
file_size -> Integer,
akey -> Nullable<Text>,
}
}
table! {
ciphers (uuid) {
uuid -> Text,
created_at -> Timestamp,
updated_at -> Timestamp,
user_uuid -> Nullable<Text>,
organization_uuid -> Nullable<Text>,
atype -> Integer,
name -> Text,
notes -> Nullable<Text>,
fields -> Nullable<Text>,
data -> Text,
password_history -> Nullable<Text>,
deleted_at -> Nullable<Timestamp>,
reprompt -> Nullable<Integer>,
}
}
table! {
ciphers_collections (cipher_uuid, collection_uuid) {
cipher_uuid -> Text,
collection_uuid -> Text,
}
}
table! {
collections (uuid) {
uuid -> Text,
org_uuid -> Text,
name -> Text,
external_id -> Nullable<Text>,
}
}
table! {
devices (uuid, user_uuid) {
uuid -> Text,
created_at -> Timestamp,
updated_at -> Timestamp,
user_uuid -> Text,
name -> Text,
atype -> Integer,
push_uuid -> Nullable<Text>,
push_token -> Nullable<Text>,
refresh_token -> Text,
twofactor_remember -> Nullable<Text>,
}
}
table! {
event (uuid) {
uuid -> Text,
event_type -> Integer,
user_uuid -> Nullable<Text>,
org_uuid -> Nullable<Text>,
cipher_uuid -> Nullable<Text>,
collection_uuid -> Nullable<Text>,
group_uuid -> Nullable<Text>,
org_user_uuid -> Nullable<Text>,
act_user_uuid -> Nullable<Text>,
device_type -> Nullable<Integer>,
ip_address -> Nullable<Text>,
event_date -> Timestamp,
policy_uuid -> Nullable<Text>,
provider_uuid -> Nullable<Text>,
provider_user_uuid -> Nullable<Text>,
provider_org_uuid -> Nullable<Text>,
}
}
table! {
favorites (user_uuid, cipher_uuid) {
user_uuid -> Text,
cipher_uuid -> Text,
}
}
table! {
folders (uuid) {
uuid -> Text,
created_at -> Timestamp,
updated_at -> Timestamp,
user_uuid -> Text,
name -> Text,
}
}
table! {
folders_ciphers (cipher_uuid, folder_uuid) {
cipher_uuid -> Text,
folder_uuid -> Text,
}
}
table! {
invitations (email) {
email -> Text,
}
}
table! {
org_policies (uuid) {
uuid -> Text,
org_uuid -> Text,
atype -> Integer,
enabled -> Bool,
data -> Text,
}
}
table! {
organizations (uuid) {
uuid -> Text,
name -> Text,
billing_email -> Text,
private_key -> Nullable<Text>,
public_key -> Nullable<Text>,
}
}
table! {
sends (uuid) {
uuid -> Text,
user_uuid -> Nullable<Text>,
organization_uuid -> Nullable<Text>,
name -> Text,
notes -> Nullable<Text>,
atype -> Integer,
data -> Text,
akey -> Text,
password_hash -> Nullable<Binary>,
password_salt -> Nullable<Binary>,
password_iter -> Nullable<Integer>,
max_access_count -> Nullable<Integer>,
access_count -> Integer,
creation_date -> Timestamp,
revision_date -> Timestamp,
expiration_date -> Nullable<Timestamp>,
deletion_date -> Timestamp,
disabled -> Bool,
hide_email -> Nullable<Bool>,
}
}
table! {
twofactor (uuid) {
uuid -> Text,
user_uuid -> Text,
atype -> Integer,
enabled -> Bool,
data -> Text,
last_used -> Integer,
}
}
table! {
twofactor_incomplete (user_uuid, device_uuid) {
user_uuid -> Text,
device_uuid -> Text,
device_name -> Text,
login_time -> Timestamp,
ip_address -> Text,
}
}
table! {
users (uuid) {
uuid -> Text,
enabled -> Bool,
created_at -> Timestamp,
updated_at -> Timestamp,
verified_at -> Nullable<Timestamp>,
last_verifying_at -> Nullable<Timestamp>,
login_verify_count -> Integer,
email -> Text,
email_new -> Nullable<Text>,
email_new_token -> Nullable<Text>,
name -> Text,
password_hash -> Binary,
salt -> Binary,
password_iterations -> Integer,
password_hint -> Nullable<Text>,
akey -> Text,
private_key -> Nullable<Text>,
public_key -> Nullable<Text>,
totp_secret -> Nullable<Text>,
totp_recover -> Nullable<Text>,
security_stamp -> Text,
stamp_exception -> Nullable<Text>,
equivalent_domains -> Text,
excluded_globals -> Text,
client_kdf_type -> Integer,
client_kdf_iter -> Integer,
client_kdf_memory -> Nullable<Integer>,
client_kdf_parallelism -> Nullable<Integer>,
api_key -> Nullable<Text>,
avatar_color -> Nullable<Text>,
external_id -> Nullable<Text>,
}
}
table! {
users_collections (user_uuid, collection_uuid) {
user_uuid -> Text,
collection_uuid -> Text,
read_only -> Bool,
hide_passwords -> Bool,
}
}
table! {
users_organizations (uuid) {
uuid -> Text,
user_uuid -> Text,
org_uuid -> Text,
access_all -> Bool,
akey -> Text,
status -> Integer,
atype -> Integer,
reset_password_key -> Nullable<Text>,
external_id -> Nullable<Text>,
}
}
table! {
organization_api_key (uuid, org_uuid) {
uuid -> Text,
org_uuid -> Text,
atype -> Integer,
api_key -> Text,
revision_date -> Timestamp,
}
}
table! {
emergency_access (uuid) {
uuid -> Text,
grantor_uuid -> Text,
grantee_uuid -> Nullable<Text>,
email -> Nullable<Text>,
key_encrypted -> Nullable<Text>,
atype -> Integer,
status -> Integer,
wait_time_days -> Integer,
recovery_initiated_at -> Nullable<Timestamp>,
last_notification_at -> Nullable<Timestamp>,
updated_at -> Timestamp,
created_at -> Timestamp,
}
}
table! {
groups (uuid) {
uuid -> Text,
organizations_uuid -> Text,
name -> Text,
access_all -> Bool,
external_id -> Nullable<Text>,
creation_date -> Timestamp,
revision_date -> Timestamp,
}
}
table! {
groups_users (groups_uuid, users_organizations_uuid) {
groups_uuid -> Text,
users_organizations_uuid -> Text,
}
}
table! {
collections_groups (collections_uuid, groups_uuid) {
collections_uuid -> Text,
groups_uuid -> Text,
read_only -> Bool,
hide_passwords -> Bool,
}
}
table! {
auth_requests (uuid) {
uuid -> Text,
user_uuid -> Text,
organization_uuid -> Nullable<Text>,
request_device_identifier -> Text,
device_type -> Integer,
request_ip -> Text,
response_device_id -> Nullable<Text>,
access_code -> Text,
public_key -> Text,
enc_key -> Nullable<Text>,
master_password_hash -> Nullable<Text>,
approved -> Nullable<Bool>,
creation_date -> Timestamp,
response_date -> Nullable<Timestamp>,
authentication_date -> Nullable<Timestamp>,
}
}
joinable!(attachments -> ciphers (cipher_uuid));
joinable!(ciphers -> organizations (organization_uuid));
joinable!(ciphers -> users (user_uuid));
joinable!(ciphers_collections -> ciphers (cipher_uuid));
joinable!(ciphers_collections -> collections (collection_uuid));
joinable!(collections -> organizations (org_uuid));
joinable!(devices -> users (user_uuid));
joinable!(folders -> users (user_uuid));
joinable!(folders_ciphers -> ciphers (cipher_uuid));
joinable!(folders_ciphers -> folders (folder_uuid));
joinable!(org_policies -> organizations (org_uuid));
joinable!(sends -> organizations (organization_uuid));
joinable!(sends -> users (user_uuid));
joinable!(twofactor -> users (user_uuid));
joinable!(users_collections -> collections (collection_uuid));
joinable!(users_collections -> users (user_uuid));
joinable!(users_organizations -> organizations (org_uuid));
joinable!(users_organizations -> users (user_uuid));
joinable!(users_organizations -> ciphers (org_uuid));
joinable!(organization_api_key -> organizations (org_uuid));
joinable!(emergency_access -> users (grantor_uuid));
joinable!(groups -> organizations (organizations_uuid));
joinable!(groups_users -> users_organizations (users_organizations_uuid));
joinable!(groups_users -> groups (groups_uuid));
joinable!(collections_groups -> collections (collections_uuid));
joinable!(collections_groups -> groups (groups_uuid));
joinable!(event -> users_organizations (uuid));
joinable!(auth_requests -> users (user_uuid));
allow_tables_to_appear_in_same_query!(
attachments,
ciphers,
ciphers_collections,
collections,
devices,
folders,
folders_ciphers,
invitations,
org_policies,
organizations,
sends,
twofactor,
users,
users_collections,
users_organizations,
organization_api_key,
emergency_access,
groups,
groups_users,
collections_groups,
event,
auth_requests,
);

Datei anzeigen

@ -115,7 +115,7 @@ async fn main() -> Result<(), Error> {
let pool = create_db_pool().await; let pool = create_db_pool().await;
schedule_jobs(pool.clone()); schedule_jobs(pool.clone());
crate::db::models::TwoFactor::migrate_u2f_to_webauthn(&mut pool.get().await.unwrap()).await.unwrap(); crate::db::models::TwoFactor::migrate_u2f_to_webauthn(&pool.get().await.unwrap()).await.unwrap();
launch_rocket(pool, extra_debug).await // Blocks until program termination. launch_rocket(pool, extra_debug).await // Blocks until program termination.
} }