|
|
@@ -23,7 +23,7 @@ pub fn routes() -> Vec<Route> {
|
|
|
|
|
|
routes![
|
|
|
admin_login,
|
|
|
- get_users,
|
|
|
+ get_users_json,
|
|
|
post_admin_login,
|
|
|
admin_page,
|
|
|
invite_user,
|
|
|
@@ -36,6 +36,9 @@ pub fn routes() -> Vec<Route> {
|
|
|
delete_config,
|
|
|
backup_db,
|
|
|
test_smtp,
|
|
|
+ users_overview,
|
|
|
+ organizations_overview,
|
|
|
+ diagnostics,
|
|
|
]
|
|
|
}
|
|
|
|
|
|
@@ -118,7 +121,9 @@ fn _validate_token(token: &str) -> bool {
|
|
|
struct AdminTemplateData {
|
|
|
page_content: String,
|
|
|
version: Option<&'static str>,
|
|
|
- users: Vec<Value>,
|
|
|
+ users: Option<Vec<Value>>,
|
|
|
+ organizations: Option<Vec<Value>>,
|
|
|
+ diagnostics: Option<Value>,
|
|
|
config: Value,
|
|
|
can_backup: bool,
|
|
|
logged_in: bool,
|
|
|
@@ -126,15 +131,59 @@ struct AdminTemplateData {
|
|
|
}
|
|
|
|
|
|
impl AdminTemplateData {
|
|
|
- fn new(users: Vec<Value>) -> Self {
|
|
|
+ fn new() -> Self {
|
|
|
Self {
|
|
|
- page_content: String::from("admin/page"),
|
|
|
+ page_content: String::from("admin/settings"),
|
|
|
version: VERSION,
|
|
|
- users,
|
|
|
config: CONFIG.prepare_json(),
|
|
|
can_backup: *CAN_BACKUP,
|
|
|
logged_in: true,
|
|
|
urlpath: CONFIG.domain_path(),
|
|
|
+ users: None,
|
|
|
+ organizations: None,
|
|
|
+ diagnostics: None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn users(users: Vec<Value>) -> Self {
|
|
|
+ Self {
|
|
|
+ page_content: String::from("admin/users"),
|
|
|
+ version: VERSION,
|
|
|
+ users: Some(users),
|
|
|
+ config: CONFIG.prepare_json(),
|
|
|
+ can_backup: *CAN_BACKUP,
|
|
|
+ logged_in: true,
|
|
|
+ urlpath: CONFIG.domain_path(),
|
|
|
+ organizations: None,
|
|
|
+ diagnostics: None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn organizations(organizations: Vec<Value>) -> Self {
|
|
|
+ Self {
|
|
|
+ page_content: String::from("admin/organizations"),
|
|
|
+ version: VERSION,
|
|
|
+ organizations: Some(organizations),
|
|
|
+ config: CONFIG.prepare_json(),
|
|
|
+ can_backup: *CAN_BACKUP,
|
|
|
+ logged_in: true,
|
|
|
+ urlpath: CONFIG.domain_path(),
|
|
|
+ users: None,
|
|
|
+ diagnostics: None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn diagnostics(diagnostics: Value) -> Self {
|
|
|
+ Self {
|
|
|
+ page_content: String::from("admin/diagnostics"),
|
|
|
+ version: VERSION,
|
|
|
+ organizations: None,
|
|
|
+ config: CONFIG.prepare_json(),
|
|
|
+ can_backup: *CAN_BACKUP,
|
|
|
+ logged_in: true,
|
|
|
+ urlpath: CONFIG.domain_path(),
|
|
|
+ users: None,
|
|
|
+ diagnostics: Some(diagnostics),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -144,11 +193,8 @@ impl AdminTemplateData {
|
|
|
}
|
|
|
|
|
|
#[get("/", rank = 1)]
|
|
|
-fn admin_page(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
|
|
|
- let users = User::get_all(&conn);
|
|
|
- let users_json: Vec<Value> = users.iter().map(|u| u.to_json(&conn)).collect();
|
|
|
-
|
|
|
- let text = AdminTemplateData::new(users_json).render()?;
|
|
|
+fn admin_page(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
|
|
|
+ let text = AdminTemplateData::new().render()?;
|
|
|
Ok(Html(text))
|
|
|
}
|
|
|
|
|
|
@@ -195,13 +241,29 @@ fn logout(mut cookies: Cookies) -> Result<Redirect, ()> {
|
|
|
}
|
|
|
|
|
|
#[get("/users")]
|
|
|
-fn get_users(_token: AdminToken, conn: DbConn) -> JsonResult {
|
|
|
+fn get_users_json(_token: AdminToken, conn: DbConn) -> JsonResult {
|
|
|
let users = User::get_all(&conn);
|
|
|
let users_json: Vec<Value> = users.iter().map(|u| u.to_json(&conn)).collect();
|
|
|
|
|
|
Ok(Json(Value::Array(users_json)))
|
|
|
}
|
|
|
|
|
|
+#[get("/users/overview")]
|
|
|
+fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
|
|
|
+ let users = User::get_all(&conn);
|
|
|
+ let users_json: Vec<Value> = users.iter()
|
|
|
+ .map(|u| {
|
|
|
+ let mut usr = u.to_json(&conn);
|
|
|
+ if let Some(ciphers) = Cipher::count_owned_by_user(&u.uuid, &conn) {
|
|
|
+ usr["cipher_count"] = json!(ciphers);
|
|
|
+ };
|
|
|
+ usr
|
|
|
+ }).collect();
|
|
|
+
|
|
|
+ let text = AdminTemplateData::users(users_json).render()?;
|
|
|
+ Ok(Html(text))
|
|
|
+}
|
|
|
+
|
|
|
#[post("/users/<uuid>/delete")]
|
|
|
fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
|
|
|
let user = match User::find_by_uuid(&uuid, &conn) {
|
|
|
@@ -242,6 +304,92 @@ fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
|
|
|
User::update_all_revisions(&conn)
|
|
|
}
|
|
|
|
|
|
+#[get("/organizations/overview")]
|
|
|
+fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
|
|
|
+ let organizations = Organization::get_all(&conn);
|
|
|
+ let organizations_json: Vec<Value> = organizations.iter().map(|o| o.to_json()).collect();
|
|
|
+
|
|
|
+ let text = AdminTemplateData::organizations(organizations_json).render()?;
|
|
|
+ Ok(Html(text))
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Deserialize, Serialize, Debug)]
|
|
|
+#[allow(non_snake_case)]
|
|
|
+pub struct WebVaultVersion {
|
|
|
+ version: String,
|
|
|
+}
|
|
|
+
|
|
|
+fn get_github_api(url: &str) -> Result<Value, Error> {
|
|
|
+ use reqwest::{header::USER_AGENT, blocking::Client};
|
|
|
+ let github_api = Client::builder().build()?;
|
|
|
+
|
|
|
+ let res = github_api
|
|
|
+ .get(url)
|
|
|
+ .header(USER_AGENT, "Bitwarden_RS")
|
|
|
+ .send()?;
|
|
|
+
|
|
|
+ let res_status = res.status();
|
|
|
+ if res_status != 200 {
|
|
|
+ error!("Could not retrieve '{}', response code: {}", url, res_status);
|
|
|
+ }
|
|
|
+
|
|
|
+ let value: Value = res.error_for_status()?.json()?;
|
|
|
+ Ok(value)
|
|
|
+}
|
|
|
+
|
|
|
+#[get("/diagnostics")]
|
|
|
+fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
|
|
|
+ use std::net::ToSocketAddrs;
|
|
|
+ use chrono::prelude::*;
|
|
|
+ use crate::util::read_file_string;
|
|
|
+
|
|
|
+ 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 github_ips = ("github.com", 0).to_socket_addrs().map(|mut i| i.next());
|
|
|
+ let dns_resolved = match github_ips {
|
|
|
+ Ok(Some(a)) => a.ip().to_string(),
|
|
|
+ _ => "Could not resolve domain name.".to_string(),
|
|
|
+ };
|
|
|
+
|
|
|
+ let bitwarden_rs_releases = get_github_api("https://api.github.com/repos/dani-garcia/bitwarden_rs/releases/latest");
|
|
|
+ let latest_release = match &bitwarden_rs_releases {
|
|
|
+ Ok(j) => j["tag_name"].as_str().unwrap(),
|
|
|
+ _ => "-",
|
|
|
+ };
|
|
|
+
|
|
|
+ let bitwarden_rs_commits = get_github_api("https://api.github.com/repos/dani-garcia/bitwarden_rs/commits/master");
|
|
|
+ let mut latest_commit = match &bitwarden_rs_commits {
|
|
|
+ Ok(j) => j["sha"].as_str().unwrap(),
|
|
|
+ _ => "-",
|
|
|
+ };
|
|
|
+ if latest_commit.len() >= 8 {
|
|
|
+ latest_commit = &latest_commit[..8];
|
|
|
+ }
|
|
|
+
|
|
|
+ let bw_web_builds_releases = get_github_api("https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest");
|
|
|
+ let latest_web_build = match &bw_web_builds_releases {
|
|
|
+ Ok(j) => j["tag_name"].as_str().unwrap(),
|
|
|
+ _ => "-",
|
|
|
+ };
|
|
|
+
|
|
|
+ let dt = Utc::now();
|
|
|
+ let server_time = dt.format("%Y-%m-%d %H:%M:%S").to_string();
|
|
|
+
|
|
|
+ let diagnostics_json = json!({
|
|
|
+ "dns_resolved": dns_resolved,
|
|
|
+ "server_time": server_time,
|
|
|
+ "web_vault_version": web_vault_version.version,
|
|
|
+ "latest_release": latest_release,
|
|
|
+ "latest_commit": latest_commit,
|
|
|
+ "latest_web_build": latest_web_build.replace("v", ""),
|
|
|
+ });
|
|
|
+
|
|
|
+ let text = AdminTemplateData::diagnostics(diagnostics_json).render()?;
|
|
|
+ Ok(Html(text))
|
|
|
+}
|
|
|
+
|
|
|
#[post("/config", data = "<data>")]
|
|
|
fn post_config(data: Json<ConfigBuilder>, _token: AdminToken) -> EmptyResult {
|
|
|
let data: ConfigBuilder = data.into_inner();
|