diff options
author | Daniel García <[email protected]> | 2019-02-05 22:17:02 +0100 |
---|---|---|
committer | Daniel García <[email protected]> | 2019-02-06 17:34:31 +0100 |
commit | 9976e4736e8fc43c91c7b6b8fa618153544154c8 (patch) | |
tree | e329c775c9e6a7a017518021aff2832ad92cbed3 /src | |
parent | dc92f072324411c0f68b62ea80d358b9e5edafaf (diff) | |
download | vaultwarden-9976e4736e8fc43c91c7b6b8fa618153544154c8.tar.gz vaultwarden-9976e4736e8fc43c91c7b6b8fa618153544154c8.zip |
Add groups
Diffstat (limited to 'src')
-rw-r--r-- | src/config.rs | 264 | ||||
-rw-r--r-- | src/static/templates/admin/base.hbs | 5 | ||||
-rw-r--r-- | src/static/templates/admin/page.hbs | 50 |
3 files changed, 177 insertions, 142 deletions
diff --git a/src/config.rs b/src/config.rs index 77e2d441..616e96c8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,8 +13,17 @@ lazy_static! { } macro_rules! make_config { - ( $( $(#[doc = $doc:literal])+ $name:ident : $ty:ty, $editable:literal, $none_action:ident $(, $default:expr)? );+ $(;)? ) => { - + ( + $( + $(#[doc = $groupdoc:literal])? + $group:ident { + $( + $(#[doc = $doc:literal])+ + $name:ident : $ty:ty, $editable:literal, $none_action:ident $(, $default:expr)?; + )+ + },)+ + + ) => { pub struct Config { inner: RwLock<Inner> } struct Inner { @@ -27,10 +36,10 @@ macro_rules! make_config { #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct ConfigBuilder { - $( + $($( #[serde(skip_serializing_if = "Option::is_none")] - $name: Option<$ty> - ),+ + $name: Option<$ty>, + )+)+ } impl ConfigBuilder { @@ -38,9 +47,9 @@ macro_rules! make_config { dotenv::dotenv().ok(); let mut builder = ConfigBuilder::default(); - $( + $($( builder.$name = get_env(&stringify!($name).to_uppercase()); - )+ + )+)+ builder } @@ -55,11 +64,11 @@ macro_rules! make_config { /// If both have the same element, `other` wins. fn merge(&self, other: &Self) -> Self { let mut builder = self.clone(); - $( + $($( if let v @Some(_) = &other.$name { builder.$name = v.clone(); } - )+ + )+)+ builder } @@ -67,21 +76,21 @@ macro_rules! make_config { /// 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(); - $( + $($( config.$name = make_config!{ @build self.$name.clone(), &config, $none_action, $($default)? }; - )+ + )+)+ config.domain_set = _domain_set; config @@ -89,15 +98,15 @@ macro_rules! make_config { } #[derive(Debug, Clone, Default)] - pub struct ConfigItems { $(pub $name: make_config!{@type $ty, $none_action} ),+ } + pub struct ConfigItems { $($(pub $name: make_config!{@type $ty, $none_action}, )+)+ } #[allow(unused)] impl Config { - $( + $($( pub fn $name(&self) -> make_config!{@type $ty, $none_action} { self.inner.read().unwrap().config.$name.clone() } - )+ + )+)+ pub fn load() -> Result<Self, Error> { // Loading from env and file @@ -122,9 +131,9 @@ macro_rules! make_config { } pub fn prepare_json(&self) -> serde_json::Value { - let cfg = { + let (def, cfg) = { let inner = &self.inner.read().unwrap(); - inner._env.merge(&inner._usr) + (inner._env.build(), inner.config.clone()) }; @@ -139,24 +148,32 @@ macro_rules! make_config { fn _get_doc(doc: &str) -> serde_json::Value { let mut split = doc.split("|>").map(str::trim); json!({ - "group": split.next(), "name": split.next(), "description": split.next() }) } - json!([ $( { - "editable": $editable, - "name": stringify!($name), - "value": cfg.$name, - "default": make_config!{ @default &cfg, $none_action, $($default)? }, - "type": _get_form_type(stringify!($ty)), - "doc": _get_doc(concat!($($doc),+)), - }, )+ ]) + json!([ $({ + "group": stringify!($group), + "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),+)), + }, )+ + ]}, )+ ]) } } }; + // Group or empty string + ( @show ) => { "" }; + ( @show $groupdoc:literal ) => { $groupdoc }; + // Wrap the optionals in an Option type ( @type $ty:ty, option) => { Option<$ty> }; ( @type $ty:ty, $id:ident) => { $ty }; @@ -173,108 +190,115 @@ macro_rules! make_config { } } }}; - - // Get a default value - ( @default $config:expr, option, ) => { serde_json::Value::Null }; - ( @default $config:expr, def, $default:expr ) => { $default }; - ( @default $config:expr, auto, $default_fn:expr ) => {{ - let f: &Fn(ConfigItems) -> _ = &$default_fn; - f($config.build()) - }}; - } //STRUCTURE: -// /// Group |> Friendly Name |> Description (Optional) -// name: type, is_editable, none_action, <default_value (Optional)> +// /// Short description (without this they won't appear on the list) +// group { +// /// Friendly Name |> Description (Optional) +// name: type, is_editable, none_action, <default_value (Optional)> +// } // // Where none_action applied when the value wasn't provided and can be: // def: Use a default value // auto: Value is auto generated based on other values // option: Value is optional make_config! { - /// folders |> Data folder |> Main data folder - data_folder: String, false, def, "data".to_string(); - - /// folders |> Database URL - database_url: String, false, auto, |c| format!("{}/{}", c.data_folder, "db.sqlite3"); - /// folders |> Icon chache folder - icon_cache_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "icon_cache"); - /// folders |> Attachments folder - attachments_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "attachments"); - /// folders |> Templates folder - templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates"); - /// folders |> Session JWT key - rsa_key_filename: String, false, auto, |c| format!("{}/{}", c.data_folder, "rsa_key"); - - /// ws |> Enable websocket notifications - websocket_enabled: bool, false, def, false; - /// ws |> Websocket address - websocket_address: String, false, def, "0.0.0.0".to_string(); - /// ws |> Websocket port - websocket_port: u16, false, def, 3012; - - /// folders |> Web vault folder - web_vault_folder: String, false, def, "web-vault/".to_string(); - /// settings |> Enable web vault - web_vault_enabled: bool, false, def, true; - - /// icons |> Positive icon cache expiry |> Number of seconds to consider that an already cached icon is fresh. After this period, the icon will be redownloaded - icon_cache_ttl: u64, true, def, 2_592_000; - /// icons |> Negative icon cache expiry |> Number of seconds before trying to download an icon that failed again. - icon_cache_negttl: u64, true, def, 259_200; - - /// settings |> Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from $ICON_CACHE_FOLDER, - /// but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0, - /// otherwise it will delete them and they won't be downloaded again. - disable_icon_download: bool, true, def, false; - /// settings |> Allow new signups |> Controls if new users can register. Note that while this is disabled, users could still be invited - signups_allowed: bool, true, def, true; - /// settings |> Allow invitations |> Controls whether users can be invited by organization admins, even when signups are disabled - invitations_allowed: bool, true, def, true; - /// settings |> Password iterations |> Number of server-side passwords hashing iterations. The changes only apply when a user changes their password. Not recommended to lower the value - password_iterations: i32, true, def, 100_000; - /// settings |> Show password hints |> Controls if the password hint should be shown directly in the web page. Otherwise, if email is disabled, there is no way to see the password hint - show_password_hint: bool, true, def, true; - - /// settings |> Domain URL |> This needs to be set to the URL used to access the server, including 'http[s]://' and port, if it's different than the default. Some server functions don't work correctly without this value - domain: String, true, def, "http://localhost".to_string(); - /// private |> Domain set - domain_set: bool, false, def, false; - - /// settings |> Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request. ONLY use this during development, as it can slow down the server - reload_templates: bool, true, def, false; - - /// log |> Enable extended logging - extended_logging: bool, false, def, true; - /// log |> Log file path - log_file: String, false, option; - - /// settings |> Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session - admin_token: String, true, option; - - /// yubico |> Yubico Client ID - yubico_client_id: String, true, option; - /// yubico |> Yubico secret Key - yubico_secret_key: String, true, option; - /// yubico |> Yubico Server - yubico_server: String, true, option; - - // TODO: Remove SMTP from name once groups work - /// mail |> SMTP Host - smtp_host: String, true, option; - /// mail |> Enable SMTP SSL - smtp_ssl: bool, true, def, true; - /// mail |> SMTP Port - smtp_port: u16, true, auto, |c| if c.smtp_ssl {587} else {25}; - /// mail |> SMTP From Address - smtp_from: String, true, def, String::new(); - /// mail |> SMTP From Name - smtp_from_name: String, true, def, "Bitwarden_RS".to_string(); - /// mail |> SMTP Username - smtp_username: String, true, option; - /// mail |> SMTP Password - smtp_password: String, true, option; + folders { + /// Data folder |> Main data folder + data_folder: String, false, def, "data".to_string(); + + /// Database URL + database_url: String, false, auto, |c| format!("{}/{}", c.data_folder, "db.sqlite3"); + /// Icon chache folder + icon_cache_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "icon_cache"); + /// Attachments folder + attachments_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "attachments"); + /// Templates folder + templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates"); + /// Session JWT key + rsa_key_filename: String, false, auto, |c| format!("{}/{}", c.data_folder, "rsa_key"); + /// Web vault folder + web_vault_folder: String, false, def, "web-vault/".to_string(); + }, + ws { + /// Enable websocket notifications + websocket_enabled: bool, false, def, false; + /// Websocket address + websocket_address: String, false, def, "0.0.0.0".to_string(); + /// Websocket port + websocket_port: u16, false, def, 3012; + }, + + /// General settings + settings { + /// Domain URL |> This needs to be set to the URL used to access the server, including 'http[s]://' and port, if it's different than the default. Some server functions don't work correctly without this value + domain: String, true, def, "http://localhost".to_string(); + /// PRIVATE |> Domain set + domain_set: bool, false, def, false; + /// Enable web vault + web_vault_enabled: bool, false, def, true; + + /// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from $ICON_CACHE_FOLDER, + /// but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0, + /// otherwise it will delete them and they won't be downloaded again. + disable_icon_download: bool, true, def, false; + /// Allow new signups |> Controls if new users can register. Note that while this is disabled, users could still be invited + signups_allowed: bool, true, def, true; + /// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are disabled + invitations_allowed: bool, true, def, true; + /// Password iterations |> Number of server-side passwords hashing iterations. The changes only apply when a user changes their password. Not recommended to lower the value + password_iterations: i32, true, def, 100_000; + /// Show password hints |> Controls if the password hint should be shown directly in the web page. Otherwise, if email is disabled, there is no way to see the password hint + show_password_hint: bool, true, def, true; + + /// Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session + admin_token: String, true, option; + }, + + /// Advanced settings + advanced { + /// Positive icon cache expiry |> Number of seconds to consider that an already cached icon is fresh. After this period, the icon will be redownloaded + icon_cache_ttl: u64, true, def, 2_592_000; + /// Negative icon cache expiry |> Number of seconds before trying to download an icon that failed again. + icon_cache_negttl: u64, true, def, 259_200; + + /// Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request. ONLY use this during development, as it can slow down the server + reload_templates: bool, true, def, false; + + /// Enable extended logging + extended_logging: bool, false, def, true; + /// Log file path + log_file: String, false, option; + }, + + /// Yubikey settings + yubico { + /// Client ID + yubico_client_id: String, true, option; + /// Secret Key + yubico_secret_key: String, true, option; + /// Server + yubico_server: String, true, option; + }, + + /// SMTP Email Settings + smtp { + /// Host + smtp_host: String, true, option; + /// Enable SSL + smtp_ssl: bool, true, def, true; + /// Port + smtp_port: u16, true, auto, |c| if c.smtp_ssl {587} else {25}; + /// From Address + smtp_from: String, true, def, String::new(); + /// From Name + smtp_from_name: String, true, def, "Bitwarden_RS".to_string(); + /// Username + smtp_username: String, true, option; + /// Password + smtp_password: String, true, option; + }, } fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { diff --git a/src/static/templates/admin/base.hbs b/src/static/templates/admin/base.hbs index a715f1b6..a718ed34 100644 --- a/src/static/templates/admin/base.hbs +++ b/src/static/templates/admin/base.hbs @@ -14,7 +14,8 @@ crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/identicon.js/2.3.3/identicon.min.js" integrity="sha256-nYoL3nK/HA1e1pJvLwNPnpKuKG9q89VFX862r5aohmA=" crossorigin="anonymous"></script> - + <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.2.1/js/bootstrap.bundle.min.js" integrity="sha256-MSYVjWgrr6UL/9eQfQvOyt6/gsxb6dpwI1zqM5DbLCs=" + crossorigin="anonymous"></script> <style> body { padding-top: 70px; @@ -41,7 +42,7 @@ </ul> </div> </nav> - + {{> (page_content) }} </body> diff --git a/src/static/templates/admin/page.hbs b/src/static/templates/admin/page.hbs index 8a041b97..5a167fa7 100644 --- a/src/static/templates/admin/page.hbs +++ b/src/static/templates/admin/page.hbs @@ -54,31 +54,41 @@ </div> </div> - <div id="config-block" class="align-items-center p-3 mb-3 text-white-50 bg-secondary rounded shadow"> + <div id="config-block" class="align-items-center p-3 mb-3 bg-secondary rounded shadow"> <div> - <h6 class="text-white">Configuration</h6> - <form class="form" id="config-form"> + <h6 class="text-white mb-3">Configuration</h6> + <form class="form accordion" id="config-form"> {{#each config}} - {{#if editable}} - <div class="form-group row" title="{{doc.description}}"> - {{#case type "text" "number"}} - <label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label> - <div class="col-sm-8"> - <input class="form-control" id="input_{{name}}" type="{{type}}" name="{{name}}" value="{{value}}" - {{#if default}} placeholder="Default: {{default}}" {{/if}}> - </div> - {{/case}} - {{#case type "checkbox"}} - <div class="col-sm-3">{{doc.name}}</div> - <div class="col-sm-8"> - <div class="form-check"> - <input class="form-check-input" type="checkbox" id="input_{{name}}" name="{{name}}" - {{#if value}} checked {{/if}}> + {{#if groupdoc}} + <div class="card bg-light mb-3"> + <div class="card-header"><button class="btn btn-link collapsed" type="button" data-toggle="collapse" + data-target="#g_{{group}}">{{groupdoc}}</button></div> + <div id="g_{{group}}" class="card-body collapse" data-parent="#config-form"> + {{#each elements}} + {{#if editable}} + <div class="form-group row" title="{{doc.description}}"> + {{#case type "text" "number"}} + <label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label> + <div class="col-sm-8"> + <input class="form-control" id="input_{{name}}" type="{{type}}" name="{{name}}" value="{{value}}" + {{#if default}} placeholder="Default: {{default}}" {{/if}}> + </div> + {{/case}} + {{#case type "checkbox"}} + <div class="col-sm-3">{{doc.name}}</div> + <div class="col-sm-8"> + <div class="form-check"> + <input class="form-check-input" type="checkbox" id="input_{{name}}" name="{{name}}" + {{#if value}} checked {{/if}}> - <label class="form-check-label" for="input_{{name}}"> Default: {{default}} </label> + <label class="form-check-label" for="input_{{name}}"> Default: {{default}} </label> + </div> + </div> + {{/case}} </div> + {{/if}} + {{/each}} </div> - {{/case}} </div> {{/if}} {{/each}} |