From 95fc88ae5bef5f4d1e9a8da4f5de7c955fb75a19 Mon Sep 17 00:00:00 2001
From: BlackDex <black.dex@gmail.com>
Date: Mon, 5 Apr 2021 15:09:16 +0200
Subject: [PATCH] Some admin interface updates.

- Fixed bug when web-vault is disabled.
- Updated sql-server version check to be simpler thx to @weiznich ( https://github.com/dani-garcia/bitwarden_rs/pull/1548#discussion_r604767196 )
- Use `VACUUM INTO` to create a SQLite backup instead of using the external sqlite3 application.
  - This also removes the dependancy of having the sqlite3 packages installed on the final image unnecessary, and thus removed it.
- Updated backup filename to also have the current time.
- Add specific bitwarden_rs web-vault version check (to match letter patched versions)
  Will work when https://github.com/dani-garcia/bw_web_builds/pull/33 is build (But still works without it also).
---
 docker/Dockerfile.j2                       |  4 --
 docker/amd64/Dockerfile                    |  1 -
 docker/amd64/Dockerfile.alpine             |  1 -
 docker/arm64/Dockerfile                    |  1 -
 docker/armv6/Dockerfile                    |  1 -
 docker/armv7/Dockerfile                    |  1 -
 docker/armv7/Dockerfile.alpine             |  1 -
 src/api/admin.rs                           | 27 +++++++----
 src/db/mod.rs                              | 56 ++++++++--------------
 src/static/templates/admin/diagnostics.hbs | 13 ++++-
 10 files changed, 50 insertions(+), 56 deletions(-)

diff --git a/docker/Dockerfile.j2 b/docker/Dockerfile.j2
index be4b4151..71630fa6 100644
--- a/docker/Dockerfile.j2
+++ b/docker/Dockerfile.j2
@@ -215,9 +215,6 @@ RUN apk add --no-cache \
         openssl \
         curl \
         dumb-init \
-{%   if "sqlite" in features %}
-        sqlite \
-{%   endif %}
 {%   if "mysql" in features %}
         mariadb-connector-c \
 {%   endif %}
@@ -232,7 +229,6 @@ RUN apt-get update && apt-get install -y \
     ca-certificates \
     curl \
     dumb-init \
-    sqlite3 \
     libmariadb-dev-compat \
     libpq5 \
     && rm -rf /var/lib/apt/lists/*
diff --git a/docker/amd64/Dockerfile b/docker/amd64/Dockerfile
index e24cca81..f524e21a 100644
--- a/docker/amd64/Dockerfile
+++ b/docker/amd64/Dockerfile
@@ -86,7 +86,6 @@ RUN apt-get update && apt-get install -y \
     ca-certificates \
     curl \
     dumb-init \
-    sqlite3 \
     libmariadb-dev-compat \
     libpq5 \
     && rm -rf /var/lib/apt/lists/*
diff --git a/docker/amd64/Dockerfile.alpine b/docker/amd64/Dockerfile.alpine
index eed79fc1..a7923d30 100644
--- a/docker/amd64/Dockerfile.alpine
+++ b/docker/amd64/Dockerfile.alpine
@@ -82,7 +82,6 @@ RUN apk add --no-cache \
         openssl \
         curl \
         dumb-init \
-        sqlite \
         postgresql-libs \
         ca-certificates
 
diff --git a/docker/arm64/Dockerfile b/docker/arm64/Dockerfile
index b6b50fbd..5ef151c5 100644
--- a/docker/arm64/Dockerfile
+++ b/docker/arm64/Dockerfile
@@ -129,7 +129,6 @@ RUN apt-get update && apt-get install -y \
     ca-certificates \
     curl \
     dumb-init \
-    sqlite3 \
     libmariadb-dev-compat \
     libpq5 \
     && rm -rf /var/lib/apt/lists/*
diff --git a/docker/armv6/Dockerfile b/docker/armv6/Dockerfile
index c6dc75e1..d86bc5d1 100644
--- a/docker/armv6/Dockerfile
+++ b/docker/armv6/Dockerfile
@@ -129,7 +129,6 @@ RUN apt-get update && apt-get install -y \
     ca-certificates \
     curl \
     dumb-init \
-    sqlite3 \
     libmariadb-dev-compat \
     libpq5 \
     && rm -rf /var/lib/apt/lists/*
diff --git a/docker/armv7/Dockerfile b/docker/armv7/Dockerfile
index 51d4c75c..ab95f629 100644
--- a/docker/armv7/Dockerfile
+++ b/docker/armv7/Dockerfile
@@ -129,7 +129,6 @@ RUN apt-get update && apt-get install -y \
     ca-certificates \
     curl \
     dumb-init \
-    sqlite3 \
     libmariadb-dev-compat \
     libpq5 \
     && rm -rf /var/lib/apt/lists/*
diff --git a/docker/armv7/Dockerfile.alpine b/docker/armv7/Dockerfile.alpine
index 14b7e9b8..07895816 100644
--- a/docker/armv7/Dockerfile.alpine
+++ b/docker/armv7/Dockerfile.alpine
@@ -86,7 +86,6 @@ RUN apk add --no-cache \
         openssl \
         curl \
         dumb-init \
-        sqlite \
         ca-certificates
 
 RUN mkdir /data
diff --git a/src/api/admin.rs b/src/api/admin.rs
index d484407a..d5a743c9 100644
--- a/src/api/admin.rs
+++ b/src/api/admin.rs
@@ -1,7 +1,7 @@
 use once_cell::sync::Lazy;
 use serde::de::DeserializeOwned;
 use serde_json::Value;
-use std::{env, process::Command, time::Duration};
+use std::{env, time::Duration};
 
 use reqwest::{blocking::Client, header::USER_AGENT};
 use rocket::{
@@ -68,7 +68,6 @@ static CAN_BACKUP: Lazy<bool> = Lazy::new(|| {
     DbConnType::from_url(&CONFIG.database_url())
         .map(|t| t == DbConnType::sqlite)
         .unwrap_or(false)
-        && Command::new("sqlite3").arg("-version").status().is_ok()
 });
 
 #[get("/")]
@@ -502,9 +501,17 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
     use std::net::ToSocketAddrs;
 
     // Get current running versions
-    let vault_version_path = format!("{}/{}", CONFIG.web_vault_folder(), "version.json");
-    let vault_version_str = read_file_string(&vault_version_path)?;
-    let web_vault_version: WebVaultVersion = serde_json::from_str(&vault_version_str)?;
+    let web_vault_version: WebVaultVersion = match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "bwrs-version.json")) {
+        Ok(s) => serde_json::from_str(&s)?,
+        _ => {
+            match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "version.json")) {
+                Ok(s) => serde_json::from_str(&s)?,
+                _ => {
+                    WebVaultVersion{version: String::from("Version file missing")}
+                },
+            }
+        },
+    };
 
     // Execute some environment checks
     let running_within_docker = is_running_in_docker();
@@ -557,9 +564,10 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
 
     let diagnostics_json = json!({
         "dns_resolved": dns_resolved,
-        "web_vault_version": web_vault_version.version,
         "latest_release": latest_release,
         "latest_commit": latest_commit,
+        "web_vault_enabled": &CONFIG.web_vault_enabled(),
+        "web_vault_version": web_vault_version.version,
         "latest_web_build": latest_web_build,
         "running_within_docker": running_within_docker,
         "has_http_access": has_http_access,
@@ -571,6 +579,7 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
         "db_type": *DB_TYPE,
         "db_version": get_sql_server_version(&conn),
         "admin_url": format!("{}/diagnostics", admin_url(Referer(None))),
+        "server_time_local": Local::now().format("%Y-%m-%d %H:%M:%S %Z").to_string(),
         "server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the date/time check as the last item to minimize the difference
     });
 
@@ -596,11 +605,11 @@ fn delete_config(_token: AdminToken) -> EmptyResult {
 }
 
 #[post("/config/backup_db")]
-fn backup_db(_token: AdminToken) -> EmptyResult {
+fn backup_db(_token: AdminToken, conn: DbConn) -> EmptyResult {
     if *CAN_BACKUP {
-        backup_database()
+        backup_database(&conn)
     } else {
-        err!("Can't back up current DB (either it's not SQLite or the 'sqlite' binary is not present)");
+        err!("Can't back up current DB (Only SQLite supports this feature)");
     }
 }
 
diff --git a/src/db/mod.rs b/src/db/mod.rs
index 2472caa6..83532ce2 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -1,5 +1,3 @@
-use std::process::Command;
-
 use chrono::prelude::*;
 use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
 use rocket::{
@@ -144,6 +142,7 @@ macro_rules! db_run {
     // Different code for each db
     ( @raw $conn:ident: $( $($db:ident),+ $body:block )+ ) => {
         #[allow(unused)] use diesel::prelude::*;
+        #[allow(unused_variables)]
         match $conn {
             $($(
                 #[cfg($db)]
@@ -221,21 +220,21 @@ macro_rules! db_object {
 // Reexport the models, needs to be after the macros are defined so it can access them
 pub mod models;
 
-/// Creates a back-up of the database using sqlite3
-pub fn backup_database() -> Result<(), Error> {
-    use std::path::Path;
-    let db_url = CONFIG.database_url();
-    let db_path = Path::new(&db_url).parent().unwrap();
-
-    let now: DateTime<Utc> = Utc::now();
-    let file_date = now.format("%Y%m%d").to_string();
-    let backup_command: String = format!("{}{}{}", ".backup 'db_", file_date, ".sqlite3'");
-
-    Command::new("sqlite3")
-        .current_dir(db_path)
-        .args(&["db.sqlite3", &backup_command])
-        .output()
-        .expect("Can't open database, sqlite3 is not available, make sure it's installed and available on the PATH");
+/// Creates a back-up of the sqlite database
+/// MySQL/MariaDB and PostgreSQL are not supported.
+pub fn backup_database(conn: &DbConn) -> Result<(), Error> {
+    db_run! {@raw conn:
+        postgresql, mysql {
+            err!("PostgreSQL and MySQL/MariaDB do not support this backup feature");
+        }
+        sqlite {
+            use std::path::Path;
+            let db_url = CONFIG.database_url();
+            let db_path = Path::new(&db_url).parent().unwrap().to_string_lossy();
+            let file_date = 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)?;
+        }
+    }
 
     Ok(())
 }
@@ -243,29 +242,14 @@ pub fn backup_database() -> Result<(), Error> {
 
 /// Get the SQL Server version
 pub fn get_sql_server_version(conn: &DbConn) -> String {
-    use diesel::sql_types::Text;
-    #[derive(QueryableByName)]
-    struct SqlVersion {
-        #[sql_type = "Text"]
-        version: String,
-    }
-
     db_run! {@raw conn:
         postgresql, mysql {
-            match diesel::sql_query("SELECT version() AS version;").get_result::<SqlVersion>(conn).ok() {
-                Some(v) => {
-                    v.version
-                },
-                _ => "Unknown".to_string()
-            }
+            no_arg_sql_function!(version, diesel::sql_types::Text);
+            diesel::select(version).get_result::<String>(conn).unwrap_or_else(|_| "Unknown".to_string())
         }
         sqlite {
-            match diesel::sql_query("SELECT sqlite_version() AS version;").get_result::<SqlVersion>(conn).ok() {
-                Some(v) => {
-                    v.version
-                },
-                _ => "Unknown".to_string()
-            }
+            no_arg_sql_function!(sqlite_version, diesel::sql_types::Text);
+            diesel::select(sqlite_version).get_result::<String>(conn).unwrap_or_else(|_| "Unknown".to_string())
         }
     }
 }
diff --git a/src/static/templates/admin/diagnostics.hbs b/src/static/templates/admin/diagnostics.hbs
index 8d7901db..1d5ca711 100644
--- a/src/static/templates/admin/diagnostics.hbs
+++ b/src/static/templates/admin/diagnostics.hbs
@@ -20,6 +20,7 @@
                     <dd class="col-sm-7">
                         <span id="server-latest">{{diagnostics.latest_release}}<span id="server-latest-commit" class="d-none">-{{diagnostics.latest_commit}}</span></span>
                     </dd>
+                    {{#if diagnostics.web_vault_enabled}}
                     <dt class="col-sm-5">Web Installed
                         <span class="badge badge-success d-none" id="web-success" title="Latest version is installed.">Ok</span>
                         <span class="badge badge-warning d-none" id="web-warning" title="There seems to be an update available.">Update</span>
@@ -35,6 +36,13 @@
                         <span id="web-latest">{{diagnostics.latest_web_build}}</span>
                     </dd>
                     {{/unless}}
+                    {{/if}}
+                    {{#unless diagnostics.web_vault_enabled}}
+                    <dt class="col-sm-5">Web Installed</dt>
+                    <dd class="col-sm-7">
+                        <span id="web-installed">Web Vault is disabled</span>
+                    </dd>
+                    {{/unless}}
                     <dt class="col-sm-5">Database</dt>
                     <dd class="col-sm-7">
                         <span><b>{{diagnostics.db_type}}:</b> {{diagnostics.db_version}}</span>
@@ -118,7 +126,10 @@
                     <dd class="col-sm-7">
                         <span id="dns-resolved">{{diagnostics.dns_resolved}}</span>
                     </dd>
-
+                    <dt class="col-sm-5">Date & Time (Local)</dt>
+                    <dd class="col-sm-7">
+                        <span><b>Server:</b> {{diagnostics.server_time_local}}</span>
+                    </dd>
                     <dt class="col-sm-5">Date & Time (UTC)
                         <span class="badge badge-success d-none" id="time-success" title="Time offsets seem to be correct.">Ok</span>
                         <span class="badge badge-danger d-none" id="time-warning" title="Time offsets are too mouch at drift.">Error</span>