aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBlackDex <[email protected]>2021-11-05 19:18:54 +0100
committerBlackDex <[email protected]>2021-11-06 17:44:53 +0100
commitc453528dc10d92c76776a7c96dd8de178a14cfff (patch)
tree223362f75c44f8711589ee1d206738b171a86fca /src
parent6ae48aa8c258c0b3d53966e6abb2d9756e65f56d (diff)
downloadvaultwarden-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.rs5
-rw-r--r--src/api/core/accounts.rs4
-rw-r--r--src/api/core/organizations.rs62
-rw-r--r--src/api/notifications.rs4
-rw-r--r--src/auth.rs1
-rw-r--r--src/config.rs153
-rw-r--r--src/db/models/two_factor.rs1
-rw-r--r--src/error.rs2
-rw-r--r--src/mail.rs10
-rw-r--r--src/main.rs6
-rw-r--r--src/util.rs17
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(),