diff options
author | BlackDex <[email protected]> | 2021-11-05 19:18:54 +0100 |
---|---|---|
committer | BlackDex <[email protected]> | 2021-11-06 17:44:53 +0100 |
commit | c453528dc10d92c76776a7c96dd8de178a14cfff (patch) | |
tree | 223362f75c44f8711589ee1d206738b171a86fca /src | |
parent | 6ae48aa8c258c0b3d53966e6abb2d9756e65f56d (diff) | |
download | vaultwarden-c453528dc10d92c76776a7c96dd8de178a14cfff.tar.gz vaultwarden-c453528dc10d92c76776a7c96dd8de178a14cfff.zip |
Macro recursion decrease and other optimizations
- Decreased `recursion_limit` from 512 to 87
Mainly done by optimizing the config macro's.
This fixes an issue with the rust-analyzer which doesn't go beyond 128
- Removed Regex for masking sensitive values and replaced it with a map()
This is much faster then using a Regex.
- Refactored the get_support_json macro's
- All items above also lowered the binary size and possibly compile-time
- Removed `_conn: DbConn` from several functions, these caused unnecessary database connections for functions who didn't used that at all
- Decreased json response for `/plans`
- Updated libraries and where needed some code changes
This also fixes some rare issues with SMTP https://github.com/lettre/lettre/issues/678
- Using Rust 2021 instead of 2018
- Updated rust nightly
Diffstat (limited to 'src')
-rw-r--r-- | src/api/admin.rs | 5 | ||||
-rw-r--r-- | src/api/core/accounts.rs | 4 | ||||
-rw-r--r-- | src/api/core/organizations.rs | 62 | ||||
-rw-r--r-- | src/api/notifications.rs | 4 | ||||
-rw-r--r-- | src/auth.rs | 1 | ||||
-rw-r--r-- | src/config.rs | 153 | ||||
-rw-r--r-- | src/db/models/two_factor.rs | 1 | ||||
-rw-r--r-- | src/error.rs | 2 | ||||
-rw-r--r-- | src/mail.rs | 10 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/util.rs | 17 |
11 files changed, 132 insertions, 133 deletions
diff --git a/src/api/admin.rs b/src/api/admin.rs index 5176ee3c..37337aee 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -236,7 +236,7 @@ impl AdminTemplateData { } #[get("/", rank = 1)] -fn admin_page(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> { +fn admin_page(_token: AdminToken) -> ApiResult<Html<String>> { let text = AdminTemplateData::new().render()?; Ok(Html(text)) } @@ -494,7 +494,6 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu // Execute some environment checks let running_within_docker = is_running_in_docker(); - let docker_base_image = docker_base_image(); let has_http_access = has_http_access(); let uses_proxy = env::var_os("HTTP_PROXY").is_some() || env::var_os("http_proxy").is_some() @@ -552,7 +551,7 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu "web_vault_version": web_vault_version.version, "latest_web_build": latest_web_build, "running_within_docker": running_within_docker, - "docker_base_image": docker_base_image, + "docker_base_image": docker_base_image(), "has_http_access": has_http_access, "ip_header_exists": &ip_header.0.is_some(), "ip_header_match": ip_header_name == CONFIG.ip_header(), diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 93636889..e3ebcde0 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -454,7 +454,7 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) } #[post("/accounts/verify-email")] -fn post_verify_email(headers: Headers, _conn: DbConn) -> EmptyResult { +fn post_verify_email(headers: Headers) -> EmptyResult { let user = headers.user; if !CONFIG.mail_enabled() { @@ -654,7 +654,7 @@ struct VerifyPasswordData { } #[post("/accounts/verify-password", data = "<data>")] -fn verify_password(data: JsonUpcase<VerifyPasswordData>, headers: Headers, _conn: DbConn) -> EmptyResult { +fn verify_password(data: JsonUpcase<VerifyPasswordData>, headers: Headers) -> EmptyResult { let data: VerifyPasswordData = data.into_inner().data; let user = headers.user; diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 00f2ef71..6b6d4547 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -1294,71 +1294,43 @@ fn put_policy( #[allow(unused_variables)] #[get("/organizations/<org_id>/tax")] -fn get_organization_tax(org_id: String, _headers: Headers, _conn: DbConn) -> EmptyResult { +fn get_organization_tax(org_id: String, _headers: Headers) -> Json<Value> { // Prevent a 404 error, which also causes Javascript errors. - err!("Only allowed when not self hosted.") + // Upstream sends "Only allowed when not self hosted." As an error message. + // If we do the same it will also output this to the log, which is overkill. + // An empty list/data also works fine. + Json(_empty_data_json()) } #[get("/plans")] -fn get_plans(_headers: Headers, _conn: DbConn) -> Json<Value> { +fn get_plans(_headers: Headers) -> Json<Value> { + // Respond with a minimal json just enough to allow the creation of an new organization. Json(json!({ "Object": "list", - "Data": [ - { + "Data": [{ "Object": "plan", "Type": 0, "Product": 0, "Name": "Free", - "IsAnnual": false, "NameLocalizationKey": "planNameFree", - "DescriptionLocalizationKey": "planDescFree", - "CanBeUsedByBusiness": false, - "BaseSeats": 2, - "BaseStorageGb": null, - "MaxCollections": 2, - "MaxUsers": 2, - "HasAdditionalSeatsOption": false, - "MaxAdditionalSeats": null, - "HasAdditionalStorageOption": false, - "MaxAdditionalStorage": null, - "HasPremiumAccessOption": false, - "TrialPeriodDays": null, - "HasSelfHost": false, - "HasPolicies": false, - "HasGroups": false, - "HasDirectory": false, - "HasEvents": false, - "HasTotp": false, - "Has2fa": false, - "HasApi": false, - "HasSso": false, - "UsersGetPremium": false, - "UpgradeSortOrder": -1, - "DisplaySortOrder": -1, - "LegacyYear": null, - "Disabled": false, - "StripePlanId": null, - "StripeSeatPlanId": null, - "StripeStoragePlanId": null, - "StripePremiumAccessPlanId": null, - "BasePrice": 0.0, - "SeatPrice": 0.0, - "AdditionalStoragePricePerGb": 0.0, - "PremiumAccessOptionPrice": 0.0 - } - ], + "DescriptionLocalizationKey": "planDescFree" + }], "ContinuationToken": null })) } #[get("/plans/sales-tax-rates")] -fn get_plans_tax_rates(_headers: Headers, _conn: DbConn) -> Json<Value> { +fn get_plans_tax_rates(_headers: Headers) -> Json<Value> { // Prevent a 404 error, which also causes Javascript errors. - Json(json!({ + Json(_empty_data_json()) +} + +fn _empty_data_json() -> Value { + json!({ "Object": "list", "Data": [], "ContinuationToken": null - })) + }) } #[derive(Deserialize, Debug)] diff --git a/src/api/notifications.rs b/src/api/notifications.rs index 56985070..77539969 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -4,7 +4,7 @@ use rocket::Route; use rocket_contrib::json::Json; use serde_json::Value as JsonValue; -use crate::{api::EmptyResult, auth::Headers, db::DbConn, Error, CONFIG}; +use crate::{api::EmptyResult, auth::Headers, Error, CONFIG}; pub fn routes() -> Vec<Route> { routes![negotiate, websockets_err] @@ -30,7 +30,7 @@ fn websockets_err() -> EmptyResult { } #[post("/hub/negotiate")] -fn negotiate(_headers: Headers, _conn: DbConn) -> Json<JsonValue> { +fn negotiate(_headers: Headers) -> Json<JsonValue> { use crate::crypto; use data_encoding::BASE64URL; diff --git a/src/auth.rs b/src/auth.rs index bbcea5c8..741d4e95 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -165,7 +165,6 @@ pub fn generate_invite_claims( } } -// var token = _dataProtector.Protect($"EmergencyAccessInvite {emergencyAccess.Id} {emergencyAccess.Email} {nowMillis}"); #[derive(Debug, Serialize, Deserialize)] pub struct EmergencyAccessInviteJwtClaims { // Not before diff --git a/src/config.rs b/src/config.rs index ebf2b66f..17b39e04 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,6 @@ use std::process::exit; use std::sync::RwLock; use once_cell::sync::Lazy; -use regex::Regex; use reqwest::Url; use crate::{ @@ -23,21 +22,6 @@ pub static CONFIG: Lazy<Config> = Lazy::new(|| { }) }); -static PRIVACY_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[\w]").unwrap()); -const PRIVACY_CONFIG: &[&str] = &[ - "allowed_iframe_ancestors", - "database_url", - "domain_origin", - "domain_path", - "domain", - "helo_name", - "org_creation_users", - "signups_domains_whitelist", - "smtp_from", - "smtp_host", - "smtp_username", -]; - pub type Pass = String; macro_rules! make_config { @@ -61,7 +45,7 @@ macro_rules! make_config { _overrides: Vec<String>, } - #[derive(Debug, Clone, Default, Deserialize, Serialize)] + #[derive(Clone, Default, Deserialize, Serialize)] pub struct ConfigBuilder { $($( #[serde(skip_serializing_if = "Option::is_none")] @@ -133,19 +117,6 @@ macro_rules! make_config { builder } - /// Returns a new builder with all the elements from self, - /// except those that are equal in both sides - fn _remove(&self, other: &Self) -> Self { - let mut builder = ConfigBuilder::default(); - $($( - if &self.$name != &other.$name { - builder.$name = self.$name.clone(); - } - - )+)+ - builder - } - fn build(&self) -> ConfigItems { let mut config = ConfigItems::default(); let _domain_set = self.domain.is_some(); @@ -161,7 +132,7 @@ macro_rules! make_config { } } - #[derive(Debug, Clone, Default)] + #[derive(Clone, Default)] struct ConfigItems { $($( $name: make_config!{@type $ty, $none_action}, )+)+ } #[allow(unused)] @@ -190,38 +161,91 @@ macro_rules! make_config { fn _get_doc(doc: &str) -> serde_json::Value { let mut split = doc.split("|>").map(str::trim); - json!({ - "name": split.next(), - "description": split.next() + + // We do not use the json!() macro here since that causes a lot of macro recursion. + // This slows down compile time and it also causes issues with rust-analyzer + serde_json::Value::Object({ + let mut doc_json = serde_json::Map::new(); + doc_json.insert("name".into(), serde_json::to_value(split.next()).unwrap()); + doc_json.insert("description".into(), serde_json::to_value(split.next()).unwrap()); + doc_json }) } - json!([ $({ - "group": stringify!($group), - "grouptoggle": stringify!($($group_enabled)?), - "groupdoc": make_config!{ @show $($groupdoc)? }, - "elements": [ - $( { - "editable": $editable, - "name": stringify!($name), - "value": cfg.$name, - "default": def.$name, - "type": _get_form_type(stringify!($ty)), - "doc": _get_doc(concat!($($doc),+)), - "overridden": overriden.contains(&stringify!($name).to_uppercase()), - }, )+ - ]}, )+ ]) + // We do not use the json!() macro here since that causes a lot of macro recursion. + // This slows down compile time and it also causes issues with rust-analyzer + serde_json::Value::Array(<[_]>::into_vec(Box::new([ + $( + serde_json::Value::Object({ + let mut group = serde_json::Map::new(); + group.insert("group".into(), (stringify!($group)).into()); + group.insert("grouptoggle".into(), (stringify!($($group_enabled)?)).into()); + group.insert("groupdoc".into(), (make_config!{ @show $($groupdoc)? }).into()); + + group.insert("elements".into(), serde_json::Value::Array(<[_]>::into_vec(Box::new([ + $( + serde_json::Value::Object({ + let mut element = serde_json::Map::new(); + element.insert("editable".into(), ($editable).into()); + element.insert("name".into(), (stringify!($name)).into()); + element.insert("value".into(), serde_json::to_value(cfg.$name).unwrap()); + element.insert("default".into(), serde_json::to_value(def.$name).unwrap()); + element.insert("type".into(), (_get_form_type(stringify!($ty))).into()); + element.insert("doc".into(), (_get_doc(concat!($($doc),+))).into()); + element.insert("overridden".into(), (overriden.contains(&stringify!($name).to_uppercase())).into()); + element + }), + )+ + ])))); + group + }), + )+ + ]))) } pub fn get_support_json(&self) -> serde_json::Value { + // Define which config keys need to be masked. + // Pass types will always be masked and no need to put them in the list. + // Besides Pass, only String types will be masked via _privacy_mask. + const PRIVACY_CONFIG: &[&str] = &[ + "allowed_iframe_ancestors", + "database_url", + "domain_origin", + "domain_path", + "domain", + "helo_name", + "org_creation_users", + "signups_domains_whitelist", + "smtp_from", + "smtp_host", + "smtp_username", + ]; + let cfg = { let inner = &self.inner.read().unwrap(); inner.config.clone() }; - json!({ $($( - stringify!($name): make_config!{ @supportstr $name, cfg.$name, $ty, $none_action }, - )+)+ }) + /// We map over the string and remove all alphanumeric, _ and - characters. + /// This is the fastest way (within micro-seconds) instead of using a regex (which takes mili-seconds) + fn _privacy_mask(value: &str) -> String { + value.chars().map(|c| + match c { + c if c.is_alphanumeric() => '*', + '_' => '*', + '-' => '*', + _ => c + } + ).collect::<String>() + } + + serde_json::Value::Object({ + let mut json = serde_json::Map::new(); + $($( + json.insert(stringify!($name).into(), make_config!{ @supportstr $name, cfg.$name, $ty, $none_action }); + )+)+; + json + }) } pub fn get_overrides(&self) -> Vec<String> { @@ -229,29 +253,30 @@ macro_rules! make_config { let inner = &self.inner.read().unwrap(); inner._overrides.clone() }; - overrides } } }; // Support string print - ( @supportstr $name:ident, $value:expr, Pass, option ) => { $value.as_ref().map(|_| String::from("***")) }; // Optional pass, we map to an Option<String> with "***" - ( @supportstr $name:ident, $value:expr, Pass, $none_action:ident ) => { String::from("***") }; // Required pass, we return "***" - ( @supportstr $name:ident, $value:expr, $ty:ty, option ) => { // Optional other value, we return as is or convert to string to apply the privacy config + ( @supportstr $name:ident, $value:expr, Pass, option ) => { serde_json::to_value($value.as_ref().map(|_| String::from("***"))).unwrap() }; // Optional pass, we map to an Option<String> with "***" + ( @supportstr $name:ident, $value:expr, Pass, $none_action:ident ) => { "***".into() }; // Required pass, we return "***" + ( @supportstr $name:ident, $value:expr, String, option ) => { // Optional other value, we return as is or convert to string to apply the privacy config if PRIVACY_CONFIG.contains(&stringify!($name)) { - json!($value.as_ref().map(|x| PRIVACY_REGEX.replace_all(&x.to_string(), "${1}*").to_string())) + serde_json::to_value($value.as_ref().map(|x| _privacy_mask(x) )).unwrap() } else { - json!($value) + serde_json::to_value($value).unwrap() } }; - ( @supportstr $name:ident, $value:expr, $ty:ty, $none_action:ident ) => { // Required other value, we return as is or convert to string to apply the privacy config + ( @supportstr $name:ident, $value:expr, String, $none_action:ident ) => { // Required other value, we return as is or convert to string to apply the privacy config if PRIVACY_CONFIG.contains(&stringify!($name)) { - json!(PRIVACY_REGEX.replace_all(&$value.to_string(), "${1}*").to_string()) - } else { - json!($value) - } + _privacy_mask(&$value).into() + } else { + ($value).into() + } }; + ( @supportstr $name:ident, $value:expr, $ty:ty, option ) => { serde_json::to_value($value).unwrap() }; // Optional other value, we return as is or convert to string to apply the privacy config + ( @supportstr $name:ident, $value:expr, $ty:ty, $none_action:ident ) => { ($value).into() }; // Required other value, we return as is or convert to string to apply the privacy config // Group or empty string ( @show ) => { "" }; @@ -627,7 +652,7 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { // Check if the icon blacklist regex is valid if let Some(ref r) = cfg.icon_blacklist_regex { - let validate_regex = Regex::new(r); + let validate_regex = regex::Regex::new(r); match validate_regex { Ok(_) => (), Err(e) => err!(format!("`ICON_BLACKLIST_REGEX` is invalid: {:#?}", e)), diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs index 01505ecd..6c874df1 100644 --- a/src/db/models/two_factor.rs +++ b/src/db/models/two_factor.rs @@ -159,7 +159,6 @@ impl TwoFactor { use crate::api::core::two_factor::u2f::U2FRegistration; use crate::api::core::two_factor::webauthn::{get_webauthn_registrations, WebauthnRegistration}; - use std::convert::TryInto; use webauthn_rs::proto::*; for mut u2f in u2f_factors { diff --git a/src/error.rs b/src/error.rs index 6f11a4ea..4d6b6207 100644 --- a/src/error.rs +++ b/src/error.rs @@ -73,7 +73,7 @@ make_error! { Serde(SerdeErr): _has_source, _api_error, JWt(JwtErr): _has_source, _api_error, Handlebars(HbErr): _has_source, _api_error, - //WsError(ws::Error): _has_source, _api_error, + Io(IoErr): _has_source, _api_error, Time(TimeErr): _has_source, _api_error, Req(ReqErr): _has_source, _api_error, diff --git a/src/mail.rs b/src/mail.rs index bc1ab0f0..df9919d2 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -505,10 +505,10 @@ fn send_email(address: &str, subject: &str, body_html: String, body_text: String Err(e) => { if e.is_client() { debug!("SMTP Client error: {:#?}", e); - err!(format!("SMTP Client error: {}", e.to_string())); + err!(format!("SMTP Client error: {}", e)); } else if e.is_transient() { debug!("SMTP 4xx error: {:#?}", e); - err!(format!("SMTP 4xx error: {}", e.to_string())); + err!(format!("SMTP 4xx error: {}", e)); } else if e.is_permanent() { debug!("SMTP 5xx error: {:#?}", e); let mut msg = e.to_string(); @@ -519,13 +519,13 @@ fn send_email(address: &str, subject: &str, body_html: String, body_text: String err!(format!("SMTP 5xx error: {}", msg)); } else if e.is_timeout() { debug!("SMTP timeout error: {:#?}", e); - err!(format!("SMTP timeout error: {}", e.to_string())); + err!(format!("SMTP timeout error: {}", e)); } else if e.is_tls() { debug!("SMTP Encryption error: {:#?}", e); - err!(format!("SMTP Encryption error: {}", e.to_string())); + err!(format!("SMTP Encryption error: {}", e)); } else { debug!("SMTP {:#?}", e); - err!(format!("SMTP {}", e.to_string())); + err!(format!("SMTP {}", e)); } } } diff --git a/src/main.rs b/src/main.rs index f86efb2a..3942f9b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,10 @@ #![forbid(unsafe_code)] #![cfg_attr(feature = "unstable", feature(ip))] -#![recursion_limit = "512"] +// The recursion_limit is mainly triggered by the json!() macro. +// The more key/value pairs there are the more recursion occurs. +// We want to keep this as low as possible, but not higher then 128. +// If you go above 128 it will cause rust-analyzer to fail, +#![recursion_limit = "87"] extern crate openssl; #[macro_use] diff --git a/src/util.rs b/src/util.rs index ebf7e208..2e47077b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -282,9 +282,9 @@ pub fn delete_file(path: &str) -> IOResult<()> { res } -const UNITS: [&str; 6] = ["bytes", "KB", "MB", "GB", "TB", "PB"]; - pub fn get_display_size(size: i32) -> String { + const UNITS: [&str; 6] = ["bytes", "KB", "MB", "GB", "TB", "PB"]; + let mut size: f64 = size.into(); let mut unit_counter = 0; @@ -359,10 +359,10 @@ where try_parse_string(get_env_str_value(key)) } -const TRUE_VALUES: &[&str] = &["true", "t", "yes", "y", "1"]; -const FALSE_VALUES: &[&str] = &["false", "f", "no", "n", "0"]; - pub fn get_env_bool(key: &str) -> Option<bool> { + const TRUE_VALUES: &[&str] = &["true", "t", "yes", "y", "1"]; + const FALSE_VALUES: &[&str] = &["false", "f", "no", "n", "0"]; + match get_env_str_value(key) { Some(val) if TRUE_VALUES.contains(&val.to_lowercase().as_ref()) => Some(true), Some(val) if FALSE_VALUES.contains(&val.to_lowercase().as_ref()) => Some(false), @@ -375,7 +375,6 @@ pub fn get_env_bool(key: &str) -> Option<bool> { // use chrono::{DateTime, Local, NaiveDateTime, TimeZone}; -use chrono_tz::Tz; /// Formats a UTC-offset `NaiveDateTime` in the format used by Bitwarden API /// responses with "date" fields (`CreationDate`, `RevisionDate`, etc.). @@ -393,7 +392,7 @@ pub fn format_datetime_local(dt: &DateTime<Local>, fmt: &str) -> String { // Try parsing the `TZ` environment variable to enable formatting `%Z` as // a time zone abbreviation. if let Ok(tz) = env::var("TZ") { - if let Ok(tz) = tz.parse::<Tz>() { + if let Ok(tz) = tz.parse::<chrono_tz::Tz>() { return dt.with_timezone(&tz).format(fmt).to_string(); } } @@ -442,7 +441,7 @@ use serde_json::{self, Value}; pub type JsonMap = serde_json::Map<String, Value>; -#[derive(PartialEq, Serialize, Deserialize)] +#[derive(Serialize, Deserialize)] pub struct UpCase<T: DeserializeOwned> { #[serde(deserialize_with = "upcase_deserialize")] #[serde(flatten)] @@ -517,6 +516,8 @@ fn upcase_value(value: Value) -> Value { } } +// Inner function to handle some speciale case for the 'ssn' key. +// This key is part of the Identity Cipher (Social Security Number) fn _process_key(key: &str) -> String { match key.to_lowercase().as_ref() { "ssn" => "SSN".into(), |