From 6be26f0a386309f2b49c440a1d0a029402fb45f2 Mon Sep 17 00:00:00 2001 From: BlackDex Date: Sun, 1 Jan 2023 15:09:10 +0100 Subject: [PATCH 1/6] Fix failing large note imports When importing to Vaultwarden (or Bitwarden) notes larger then 10_000 encrypted characters are invalid. This because it for one isn't compatible with Bitwarden. And some clients tend to break on very large notes. We already added a check for this limit when adding a single cipher, but this caused issues during import, and could cause a partial imported vault. Bitwarden does some validations before actually running it through the import process and generates a special error message which helps the user indicate which items are invalid during the import. This PR adds that validation check and returns the same kind of error. Fixes #3048 --- src/api/core/ciphers.rs | 8 +++++++- src/api/core/mod.rs | 2 +- src/api/core/organizations.rs | 6 ++++++ src/db/models/cipher.rs | 29 ++++++++++++++++++++++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 9750e4b6..79e7c768 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -205,7 +205,7 @@ pub struct CipherData { */ pub Type: i32, pub Name: String, - Notes: Option, + pub Notes: Option, Fields: Option, // Only one of these should exist, depending on type @@ -542,6 +542,12 @@ async fn post_ciphers_import( let data: ImportData = data.into_inner().data; + // Validate the import before continuing + // Bitwarden does not process the import if there is one item invalid. + // Since we check for the size of the encrypted note length, we need to do that here to pre-validate it. + // TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks. + Cipher::validate_notes(&data.Ciphers)?; + // Read and create the folders let mut folders: Vec<_> = Vec::new(); for folder in data.Folders.into_iter() { diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index 3393a4d0..fb303de4 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -7,7 +7,7 @@ mod organizations; mod sends; pub mod two_factor; -pub use ciphers::{purge_trashed_ciphers, CipherSyncData, CipherSyncType}; +pub use ciphers::{purge_trashed_ciphers, CipherData, CipherSyncData, CipherSyncType}; pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job}; pub use events::{event_cleanup_job, log_event, log_user_event}; pub use sends::purge_sends; diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 60d6f714..94ef3db3 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -1378,6 +1378,12 @@ async fn post_org_import( let data: ImportData = data.into_inner().data; let org_id = query.organization_id; + // Validate the import before continuing + // Bitwarden does not process the import if there is one item invalid. + // Since we check for the size of the encrypted note length, we need to do that here to pre-validate it. + // TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks. + Cipher::validate_notes(&data.Ciphers)?; + let mut collections = Vec::new(); for coll in data.Collections { let collection = Collection::new(org_id.clone(), coll.Name); diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index da40af90..b7d26bd3 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -6,7 +6,7 @@ use super::{ Attachment, CollectionCipher, Favorite, FolderCipher, Group, User, UserOrgStatus, UserOrgType, UserOrganization, }; -use crate::api::core::CipherSyncData; +use crate::api::core::{CipherData, CipherSyncData}; use std::borrow::Cow; @@ -73,6 +73,33 @@ impl Cipher { reprompt: None, } } + + pub fn validate_notes(cipher_data: &[CipherData]) -> EmptyResult { + let mut validation_errors = serde_json::Map::new(); + for (index, cipher) in cipher_data.iter().enumerate() { + if let Some(note) = &cipher.Notes { + if note.len() > 10_000 { + validation_errors.insert( + format!("Ciphers[{index}].Notes"), + serde_json::to_value([ + "The field Notes exceeds the maximum encrypted value length of 10000 characters.", + ]) + .unwrap(), + ); + } + } + } + if !validation_errors.is_empty() { + let err_json = json!({ + "message": "The model state is invalid.", + "validationErrors" : validation_errors, + "object": "error" + }); + err_json!(err_json, "Import validation errors") + } else { + Ok(()) + } + } } use crate::db::DbConn; From 18b72da6577338c43b3f5cb274b082504091ae4b Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Sat, 7 Jan 2023 10:41:28 -0800 Subject: [PATCH 2/6] Change `text/plain` API responses to `application/json` Recent versions of the Bitwarden clients (see bitwarden/clients#3574) won't parse non-JSON responses. The most noticeable consequence is that `/api/accounts/revision-date` responses won't be parsed, leading to `/api/sync` always being called, even when it's not necessary. --- src/api/core/accounts.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 31094499..27bdb12a 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -660,9 +660,9 @@ async fn delete_account(data: JsonUpcase, headers: Headers, mut co } #[get("/accounts/revision-date")] -fn revision_date(headers: Headers) -> String { +fn revision_date(headers: Headers) -> JsonResult { let revision_date = headers.user.updated_at.timestamp_millis(); - revision_date.to_string() + Ok(Json(json!(revision_date))) } #[derive(Deserialize)] @@ -792,14 +792,11 @@ async fn rotate_api_key(data: JsonUpcase, headers: He } #[get("/devices/knowndevice//")] -async fn get_known_device(email: String, uuid: String, mut conn: DbConn) -> String { +async fn get_known_device(email: String, uuid: String, mut conn: DbConn) -> JsonResult { // This endpoint doesn't have auth header + let mut result = false; if let Some(user) = User::find_by_mail(&email, &mut conn).await { - match Device::find_by_uuid_and_user(&uuid, &user.uuid, &mut conn).await { - Some(_) => String::from("true"), - _ => String::from("false"), - } - } else { - String::from("false") + result = Device::find_by_uuid_and_user(&uuid, &user.uuid, &mut conn).await.is_some(); } + Ok(Json(json!(result))) } From 25c401f64d8f99d44f46e1a52af361255f6323c2 Mon Sep 17 00:00:00 2001 From: Rychart Redwerkz Date: Sun, 8 Jan 2023 23:18:55 +0100 Subject: [PATCH 3/6] Remove `shrink-to-fit=no` This was a workaroud needed for iOS versions before 9.3 and is not part of the recommended viewport meta tag anymore. https://www.scottohara.me/blog/2018/12/11/shrink-to-fit.html --- src/static/templates/admin/base.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/templates/admin/base.hbs b/src/static/templates/admin/base.hbs index 9b033b16..e296b114 100644 --- a/src/static/templates/admin/base.hbs +++ b/src/static/templates/admin/base.hbs @@ -2,7 +2,7 @@ - + Vaultwarden Admin Panel From e935989feec784ae32d779de18fdff5df8a59f7c Mon Sep 17 00:00:00 2001 From: BlackDex Date: Thu, 29 Dec 2022 14:11:52 +0100 Subject: [PATCH 4/6] Resolve uninlined_format_args clippy warnings The upcomming release of Rust 1.67.0 will warn on `uninlined_format_args`. This PR resolves that by inlining all these items. It also looks nicer. --- clippy.toml | 1 + src/api/core/accounts.rs | 2 +- src/api/core/ciphers.rs | 2 +- src/api/core/mod.rs | 5 ++--- src/api/core/organizations.rs | 14 +++++++------- src/api/core/sends.rs | 2 +- src/api/core/two_factor/duo.rs | 6 +++--- src/api/core/two_factor/email.rs | 2 +- src/api/icons.rs | 6 +++--- src/api/web.rs | 2 +- src/auth.rs | 14 +++++++------- src/config.rs | 18 +++++++++--------- src/db/mod.rs | 2 +- src/mail.rs | 16 ++++++++-------- src/main.rs | 12 ++++++------ src/util.rs | 6 +++--- 16 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 clippy.toml diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..16caf02e --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.60.0" diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 27bdb12a..1f932f69 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -705,7 +705,7 @@ async fn password_hint(data: JsonUpcase, mut conn: DbConn) -> mail::send_password_hint(email, hint).await?; Ok(()) } else if let Some(hint) = hint { - err!(format!("Your password hint is: {}", hint)); + err!(format!("Your password hint is: {hint}")); } else { err!(NO_HINT); } diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 79e7c768..f97403a0 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -1049,7 +1049,7 @@ async fn save_attachment( } else { attachment.delete(&mut conn).await.ok(); - err!(format!("Attachment size mismatch (expected within [{}, {}], got {})", min_size, max_size, size)); + err!(format!("Attachment size mismatch (expected within [{min_size}, {max_size}], got {size})")); } } else { // Legacy API diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index fb303de4..d029cb60 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -172,8 +172,7 @@ async fn put_eq_domains( #[get("/hibp/breach?")] async fn hibp_breach(username: String) -> JsonResult { let url = format!( - "https://haveibeenpwned.com/api/v3/breachedaccount/{}?truncateResponse=false&includeUnverified=false", - username + "https://haveibeenpwned.com/api/v3/breachedaccount/{username}?truncateResponse=false&includeUnverified=false" ); if let Some(api_key) = crate::CONFIG.hibp_api_key() { @@ -195,7 +194,7 @@ async fn hibp_breach(username: String) -> JsonResult { "Domain": "haveibeenpwned.com", "BreachDate": "2019-08-18T00:00:00Z", "AddedDate": "2019-08-18T00:00:00Z", - "Description": format!("Go to: https://haveibeenpwned.com/account/{account} for a manual check.

HaveIBeenPwned API key not set!
Go to https://haveibeenpwned.com/API/Key to purchase an API key from HaveIBeenPwned.

", account=username), + "Description": format!("Go to: https://haveibeenpwned.com/account/{username} for a manual check.

HaveIBeenPwned API key not set!
Go to https://haveibeenpwned.com/API/Key to purchase an API key from HaveIBeenPwned.

"), "LogoPath": "vw_static/hibp.png", "PwnCount": 0, "DataClasses": [ diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 94ef3db3..3fb83ae2 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -713,7 +713,7 @@ async fn send_invite( let user = match User::find_by_mail(&email, &mut conn).await { None => { if !CONFIG.invitations_allowed() { - err!(format!("User does not exist: {}", email)) + err!(format!("User does not exist: {email}")) } if !CONFIG.is_email_domain_allowed(&email) { @@ -731,7 +731,7 @@ async fn send_invite( } Some(user) => { if UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &mut conn).await.is_some() { - err!(format!("User already in organization: {}", email)) + err!(format!("User already in organization: {email}")) } else { // automatically accept existing users if mail is disabled if !CONFIG.mail_enabled() && !user.password_hash.is_empty() { @@ -808,7 +808,7 @@ async fn bulk_reinvite_user( for org_user_id in data.Ids { let err_msg = match _reinvite_user(&org_id, &org_user_id, &headers.user.email, &mut conn).await { Ok(_) => String::new(), - Err(e) => format!("{:?}", e), + Err(e) => format!("{e:?}"), }; bulk_response.push(json!( @@ -970,7 +970,7 @@ async fn bulk_confirm_invite( let err_msg = match _confirm_invite(&org_id, org_user_id, user_key, &headers, &mut conn, &ip, &nt).await { Ok(_) => String::new(), - Err(e) => format!("{:?}", e), + Err(e) => format!("{e:?}"), }; bulk_response.push(json!( @@ -1224,7 +1224,7 @@ async fn bulk_delete_user( for org_user_id in data.Ids { let err_msg = match _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip, &nt).await { Ok(_) => String::new(), - Err(e) => format!("{:?}", e), + Err(e) => format!("{e:?}"), }; bulk_response.push(json!( @@ -1825,7 +1825,7 @@ async fn bulk_revoke_organization_user( let org_user_id = org_user_id.as_str().unwrap_or_default(); let err_msg = match _revoke_organization_user(&org_id, org_user_id, &headers, &mut conn, &ip).await { Ok(_) => String::new(), - Err(e) => format!("{:?}", e), + Err(e) => format!("{e:?}"), }; bulk_response.push(json!( @@ -1940,7 +1940,7 @@ async fn bulk_restore_organization_user( let org_user_id = org_user_id.as_str().unwrap_or_default(); let err_msg = match _restore_organization_user(&org_id, org_user_id, &headers, &mut conn, &ip).await { Ok(_) => String::new(), - Err(e) => format!("{:?}", e), + Err(e) => format!("{e:?}"), }; bulk_response.push(json!( diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index 7d021d08..b086663a 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -462,7 +462,7 @@ async fn post_access_file( #[get("/sends//?")] async fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> Option { if let Ok(claims) = crate::auth::decode_send(&t) { - if claims.sub == format!("{}/{}", send_id, file_id) { + if claims.sub == format!("{send_id}/{file_id}") { return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).await.ok(); } } diff --git a/src/api/core/two_factor/duo.rs b/src/api/core/two_factor/duo.rs index 06210d23..267d2db9 100644 --- a/src/api/core/two_factor/duo.rs +++ b/src/api/core/two_factor/duo.rs @@ -270,11 +270,11 @@ pub async fn generate_duo_signature(email: &str, conn: &mut DbConn) -> ApiResult let duo_sign = sign_duo_values(&sk, email, &ik, DUO_PREFIX, now + DUO_EXPIRE); let app_sign = sign_duo_values(&ak, email, &ik, APP_PREFIX, now + APP_EXPIRE); - Ok((format!("{}:{}", duo_sign, app_sign), host)) + Ok((format!("{duo_sign}:{app_sign}"), host)) } fn sign_duo_values(key: &str, email: &str, ikey: &str, prefix: &str, expire: i64) -> String { - let val = format!("{}|{}|{}", email, ikey, expire); + let val = format!("{email}|{ikey}|{expire}"); let cookie = format!("{}|{}", prefix, BASE64.encode(val.as_bytes())); format!("{}|{}", cookie, crypto::hmac_sign(key, &cookie)) @@ -327,7 +327,7 @@ fn parse_duo_values(key: &str, val: &str, ikey: &str, prefix: &str, time: i64) - let u_b64 = split[1]; let u_sig = split[2]; - let sig = crypto::hmac_sign(key, &format!("{}|{}", u_prefix, u_b64)); + let sig = crypto::hmac_sign(key, &format!("{u_prefix}|{u_b64}")); if !crypto::ct_eq(crypto::hmac_sign(key, &sig), crypto::hmac_sign(key, u_sig)) { err!("Duo signatures don't match") diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs index 9a95c465..f9a0303b 100644 --- a/src/api/core/two_factor/email.rs +++ b/src/api/core/two_factor/email.rs @@ -304,7 +304,7 @@ pub fn obscure_email(email: &str) -> String { _ => { let stars = "*".repeat(name_size - 2); name.truncate(2); - format!("{}{}", name, stars) + format!("{name}{stars}") } }; diff --git a/src/api/icons.rs b/src/api/icons.rs index 509e88c0..23d122f1 100644 --- a/src/api/icons.rs +++ b/src/api/icons.rs @@ -130,7 +130,7 @@ fn is_valid_domain(domain: &str) -> bool { const ALLOWED_CHARS: &str = "_-."; // If parsing the domain fails using Url, it will not work with reqwest. - if let Err(parse_error) = url::Url::parse(format!("https://{}", domain).as_str()) { + if let Err(parse_error) = url::Url::parse(format!("https://{domain}").as_str()) { debug!("Domain parse error: '{}' - {:?}", domain, parse_error); return false; } else if domain.is_empty() @@ -575,7 +575,7 @@ async fn get_page_with_referer(url: &str, referer: &str) -> Result c.error_for_status().map_err(Into::into), - Err(e) => err_silent!(format!("{}", e)), + Err(e) => err_silent!(format!("{e}")), } } @@ -797,7 +797,7 @@ impl reqwest::cookie::CookieStore for Jar { let cookie_store = self.0.read().unwrap(); let s = cookie_store .get_request_values(url) - .map(|(name, value)| format!("{}={}", name, value)) + .map(|(name, value)| format!("{name}={value}")) .collect::>() .join("; "); diff --git a/src/api/web.rs b/src/api/web.rs index b8d1bb51..6e3921ed 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -121,6 +121,6 @@ pub fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Er "jquery-3.6.2.slim.js" => { Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.2.slim.js"))) } - _ => err!(format!("Static file not found: {}", filename)), + _ => err!(format!("Static file not found: {filename}")), } } diff --git a/src/auth.rs b/src/auth.rs index 69c5203d..03f14cb8 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -25,16 +25,16 @@ static JWT_ADMIN_ISSUER: Lazy = Lazy::new(|| format!("{}|admin", CONFIG. static JWT_SEND_ISSUER: Lazy = Lazy::new(|| format!("{}|send", CONFIG.domain_origin())); static PRIVATE_RSA_KEY_VEC: Lazy> = Lazy::new(|| { - std::fs::read(CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key.\n{}", e)) + std::fs::read(CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key.\n{e}")) }); static PRIVATE_RSA_KEY: Lazy = Lazy::new(|| { - EncodingKey::from_rsa_pem(&PRIVATE_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding private RSA Key.\n{}", e)) + EncodingKey::from_rsa_pem(&PRIVATE_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding private RSA Key.\n{e}")) }); static PUBLIC_RSA_KEY_VEC: Lazy> = Lazy::new(|| { - std::fs::read(CONFIG.public_rsa_key()).unwrap_or_else(|e| panic!("Error loading public RSA Key.\n{}", e)) + std::fs::read(CONFIG.public_rsa_key()).unwrap_or_else(|e| panic!("Error loading public RSA Key.\n{e}")) }); static PUBLIC_RSA_KEY: Lazy = Lazy::new(|| { - DecodingKey::from_rsa_pem(&PUBLIC_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding public RSA Key.\n{}", e)) + DecodingKey::from_rsa_pem(&PUBLIC_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding public RSA Key.\n{e}")) }); pub fn load_keys() { @@ -45,7 +45,7 @@ pub fn load_keys() { pub fn encode_jwt(claims: &T) -> String { match jsonwebtoken::encode(&JWT_HEADER, claims, &PRIVATE_RSA_KEY) { Ok(token) => token, - Err(e) => panic!("Error encoding jwt {}", e), + Err(e) => panic!("Error encoding jwt {e}"), } } @@ -253,7 +253,7 @@ pub fn generate_send_claims(send_id: &str, file_id: &str) -> BasicJwtClaims { nbf: time_now.timestamp(), exp: (time_now + Duration::minutes(2)).timestamp(), iss: JWT_SEND_ISSUER.to_string(), - sub: format!("{}/{}", send_id, file_id), + sub: format!("{send_id}/{file_id}"), } } @@ -306,7 +306,7 @@ impl<'r> FromRequest<'r> for Host { "" }; - format!("{}://{}", protocol, host) + format!("{protocol}://{host}") }; Outcome::Success(Host { diff --git a/src/config.rs b/src/config.rs index 1b99cea0..fa8bea66 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,12 +13,12 @@ use crate::{ static CONFIG_FILE: Lazy = Lazy::new(|| { let data_folder = get_env("DATA_FOLDER").unwrap_or_else(|| String::from("data")); - get_env("CONFIG_FILE").unwrap_or_else(|| format!("{}/config.json", data_folder)) + get_env("CONFIG_FILE").unwrap_or_else(|| format!("{data_folder}/config.json")) }); pub static CONFIG: Lazy = Lazy::new(|| { Config::load().unwrap_or_else(|e| { - println!("Error loading config:\n\t{:?}\n", e); + println!("Error loading config:\n\t{e:?}\n"); exit(12) }) }); @@ -675,7 +675,7 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { let limit = 256; if cfg.database_max_conns < 1 || cfg.database_max_conns > limit { - err!(format!("`DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {}.", limit,)); + err!(format!("`DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {limit}.",)); } if let Some(log_file) = &cfg.log_file { @@ -764,7 +764,7 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { let validate_regex = regex::Regex::new(r); match validate_regex { Ok(_) => (), - Err(e) => err!(format!("`ICON_BLACKLIST_REGEX` is invalid: {:#?}", e)), + Err(e) => err!(format!("`ICON_BLACKLIST_REGEX` is invalid: {e:#?}")), } } @@ -774,12 +774,12 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { "internal" | "bitwarden" | "duckduckgo" | "google" => (), _ => { if !icon_service.starts_with("http") { - err!(format!("Icon service URL `{}` must start with \"http\"", icon_service)) + err!(format!("Icon service URL `{icon_service}` must start with \"http\"")) } match icon_service.matches("{}").count() { 1 => (), // nominal - 0 => err!(format!("Icon service URL `{}` has no placeholder \"{{}}\"", icon_service)), - _ => err!(format!("Icon service URL `{}` has more than one placeholder \"{{}}\"", icon_service)), + 0 => err!(format!("Icon service URL `{icon_service}` has no placeholder \"{{}}\"")), + _ => err!(format!("Icon service URL `{icon_service}` has more than one placeholder \"{{}}\"")), } } } @@ -831,7 +831,7 @@ fn extract_url_origin(url: &str) -> String { match Url::parse(url) { Ok(u) => u.origin().ascii_serialization(), Err(e) => { - println!("Error validating domain: {}", e); + println!("Error validating domain: {e}"); String::new() } } @@ -1206,7 +1206,7 @@ fn js_escape_helper<'reg, 'rc>( let mut escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27"); if !no_quote { - escaped_value = format!(""{}"", escaped_value); + escaped_value = format!(""{escaped_value}""); } out.write(&escaped_value)?; diff --git a/src/db/mod.rs b/src/db/mod.rs index ef30ffe7..c6d8343d 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -383,7 +383,7 @@ pub async fn backup_database(conn: &mut DbConn) -> Result<(), Error> { let db_url = CONFIG.database_url(); let db_path = Path::new(&db_url).parent().unwrap().to_string_lossy(); let file_date = chrono::Utc::now().format("%Y%m%d_%H%M%S").to_string(); - diesel::sql_query(format!("VACUUM INTO '{}/db_{}.sqlite3'", db_path, file_date)).execute(conn)?; + diesel::sql_query(format!("VACUUM INTO '{db_path}/db_{file_date}.sqlite3'")).execute(conn)?; Ok(()) } } diff --git a/src/mail.rs b/src/mail.rs index c77f406e..8ecb11c6 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -88,7 +88,7 @@ fn mailer() -> AsyncSmtpTransport { } fn get_text(template_name: &'static str, data: serde_json::Value) -> Result<(String, String, String), Error> { - let (subject_html, body_html) = get_template(&format!("{}.html", template_name), &data)?; + let (subject_html, body_html) = get_template(&format!("{template_name}.html"), &data)?; let (_subject_text, body_text) = get_template(template_name, &data)?; Ok((subject_html, body_html, body_text)) } @@ -531,27 +531,27 @@ async fn send_email(address: &str, subject: &str, body_html: String, body_text: Err(e) => { if e.is_client() { debug!("SMTP Client error: {:#?}", e); - err!(format!("SMTP Client error: {}", e)); + err!(format!("SMTP Client error: {e}")); } else if e.is_transient() { debug!("SMTP 4xx error: {:#?}", e); - err!(format!("SMTP 4xx error: {}", e)); + err!(format!("SMTP 4xx error: {e}")); } else if e.is_permanent() { debug!("SMTP 5xx error: {:#?}", e); let mut msg = e.to_string(); // Add a special check for 535 to add a more descriptive message if msg.contains("(535)") { - msg = format!("{} - Authentication credentials invalid", msg); + msg = format!("{msg} - Authentication credentials invalid"); } - err!(format!("SMTP 5xx error: {}", msg)); + err!(format!("SMTP 5xx error: {msg}")); } else if e.is_timeout() { debug!("SMTP timeout error: {:#?}", e); - err!(format!("SMTP timeout error: {}", e)); + err!(format!("SMTP timeout error: {e}")); } else if e.is_tls() { debug!("SMTP Encryption error: {:#?}", e); - err!(format!("SMTP Encryption error: {}", e)); + err!(format!("SMTP Encryption error: {e}")); } else { debug!("SMTP {:#?}", e); - err!(format!("SMTP {}", e)); + err!(format!("SMTP {e}")); } } } diff --git a/src/main.rs b/src/main.rs index 57b8683a..8a5f53da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -135,11 +135,11 @@ fn parse_args() { let version = VERSION.unwrap_or("(Version info from Git not present)"); if pargs.contains(["-h", "--help"]) { - println!("vaultwarden {}", version); - print!("{}", HELP); + println!("vaultwarden {version}"); + print!("{HELP}"); exit(0); } else if pargs.contains(["-v", "--version"]) { - println!("vaultwarden {}", version); + println!("vaultwarden {version}"); exit(0); } } @@ -149,7 +149,7 @@ fn launch_info() { println!("| Starting Vaultwarden |"); if let Some(version) = VERSION { - println!("|{:^68}|", format!("Version {}", version)); + println!("|{:^68}|", format!("Version {version}")); } println!("|--------------------------------------------------------------------|"); @@ -224,7 +224,7 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> { )) }); } else { - logger = logger.format(|out, message, _| out.finish(format_args!("{}", message))); + logger = logger.format(|out, message, _| out.finish(format_args!("{message}"))); } if let Some(log_file) = CONFIG.log_file() { @@ -299,7 +299,7 @@ fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch { fn create_dir(path: &str, description: &str) { // Try to create the specified dir, if it doesn't already exist. - let err_msg = format!("Error creating {} directory '{}'", description, path); + let err_msg = format!("Error creating {description} directory '{path}'"); create_dir_all(path).expect(&err_msg); } diff --git a/src/util.rs b/src/util.rs index b164c833..3297fad5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -411,16 +411,16 @@ where use std::env; pub fn get_env_str_value(key: &str) -> Option { - let key_file = format!("{}_FILE", key); + let key_file = format!("{key}_FILE"); let value_from_env = env::var(key); let value_file = env::var(&key_file); match (value_from_env, value_file) { - (Ok(_), Ok(_)) => panic!("You should not define both {} and {}!", key, key_file), + (Ok(_), Ok(_)) => panic!("You should not define both {key} and {key_file}!"), (Ok(v_env), Err(_)) => Some(v_env), (Err(_), Ok(v_file)) => match fs::read_to_string(v_file) { Ok(content) => Some(content.trim().to_string()), - Err(e) => panic!("Failed to load {}: {:?}", key, e), + Err(e) => panic!("Failed to load {key}: {e:?}"), }, _ => None, } From e266b392542f514a8d310cc7b7f7bf82bf2466fe Mon Sep 17 00:00:00 2001 From: pjsier Date: Wed, 28 Dec 2022 18:30:25 -0600 Subject: [PATCH 5/6] Log message to stderr if LOG_FILE is not writable Co-authored-by: Helmut K. C. Tessarek --- src/config.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/config.rs b/src/config.rs index fa8bea66..2dcc87e7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -684,6 +684,12 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { } } + if let Some(log_file) = &cfg.log_file { + if std::fs::OpenOptions::new().append(true).create(true).open(log_file).is_err() { + err!("Unable to write to log file", log_file); + } + } + let dom = cfg.domain.to_lowercase(); if !dom.starts_with("http://") && !dom.starts_with("https://") { err!( From 7dd1959ebac6241da855bd30d32b029d809bd6b2 Mon Sep 17 00:00:00 2001 From: pjsier Date: Wed, 28 Dec 2022 18:30:25 -0600 Subject: [PATCH 6/6] Log message to stderr if LOG_FILE is not writable Co-authored-by: Helmut K. C. Tessarek --- src/config.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/config.rs b/src/config.rs index 2dcc87e7..b21666f6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -690,6 +690,12 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { } } + if let Some(log_file) = &cfg.log_file { + if std::fs::OpenOptions::new().append(true).create(true).open(log_file).is_err() { + err!("Unable to write to log file", log_file); + } + } + let dom = cfg.domain.to_lowercase(); if !dom.starts_with("http://") && !dom.starts_with("https://") { err!(