mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-28 00:40:01 +02:00 
			
		
		
		
	
							
								
								
									
										103
									
								
								src/api/admin.rs
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								src/api/admin.rs
									
									
									
									
									
								
							| @@ -1,5 +1,6 @@ | ||||
| use once_cell::sync::Lazy; | ||||
| use serde_json::Value; | ||||
| use serde::de::DeserializeOwned; | ||||
| use std::process::Command; | ||||
|  | ||||
| use rocket::http::{Cookie, Cookies, SameSite}; | ||||
| @@ -14,6 +15,7 @@ use crate::config::ConfigBuilder; | ||||
| use crate::db::{backup_database, models::*, DbConn}; | ||||
| use crate::error::Error; | ||||
| use crate::mail; | ||||
| use crate::util::get_display_size; | ||||
| use crate::CONFIG; | ||||
|  | ||||
| pub fn routes() -> Vec<Route> { | ||||
| @@ -256,9 +258,9 @@ fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { | ||||
|     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["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn)); | ||||
|         usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, &conn)); | ||||
|         usr["attachment_size"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, &conn) as i32)); | ||||
|         usr | ||||
|     }).collect(); | ||||
|  | ||||
| @@ -309,34 +311,47 @@ fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
| #[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 organizations_json: Vec<Value> = organizations.iter().map(|o| { | ||||
|         let mut org = o.to_json(); | ||||
|         org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &conn)); | ||||
|         org["cipher_count"] = json!(Cipher::count_by_org(&o.uuid, &conn)); | ||||
|         org["attachment_count"] = json!(Attachment::count_by_org(&o.uuid, &conn)); | ||||
|         org["attachment_size"] = json!(get_display_size(Attachment::size_by_org(&o.uuid, &conn) as i32)); | ||||
|         org | ||||
|     }).collect(); | ||||
|  | ||||
|     let text = AdminTemplateData::organizations(organizations_json).render()?; | ||||
|     Ok(Html(text)) | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Serialize, Debug)] | ||||
| #[allow(non_snake_case)] | ||||
| pub struct WebVaultVersion { | ||||
| #[derive(Deserialize)] | ||||
| struct WebVaultVersion { | ||||
|     version: String, | ||||
| } | ||||
|  | ||||
| fn get_github_api(url: &str) -> Result<Value, Error> { | ||||
| #[derive(Deserialize)] | ||||
| struct GitRelease { | ||||
|     tag_name: String, | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| struct GitCommit { | ||||
|     sha: String, | ||||
| } | ||||
|  | ||||
| fn get_github_api<T: DeserializeOwned>(url: &str) -> Result<T, Error> { | ||||
|     use reqwest::{header::USER_AGENT, blocking::Client}; | ||||
|     use std::time::Duration; | ||||
|     let github_api = Client::builder().build()?; | ||||
|  | ||||
|     let res = github_api | ||||
|         .get(url) | ||||
|     Ok( | ||||
|         github_api.get(url) | ||||
|         .timeout(Duration::from_secs(10)) | ||||
|         .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) | ||||
|         .send()? | ||||
|         .error_for_status()? | ||||
|         .json::<T>()? | ||||
|     ) | ||||
| } | ||||
|  | ||||
| #[get("/diagnostics")] | ||||
| @@ -350,32 +365,36 @@ fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> { | ||||
|     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 (dns_resolved, dns_ok) = match github_ips { | ||||
|         Ok(Some(a)) => (a.ip().to_string(), true), | ||||
|         _ => ("Could not resolve domain name.".to_string(), false), | ||||
|     }; | ||||
|  | ||||
|     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(), | ||||
|         _ => "-", | ||||
|     // If the DNS Check failed, do not even attempt to check for new versions since we were not able to resolve github.com | ||||
|     let (latest_release, latest_commit, latest_web_build) = if dns_ok { | ||||
|         ( | ||||
|             match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bitwarden_rs/releases/latest") { | ||||
|                 Ok(r) => r.tag_name, | ||||
|                 _ => "-".to_string() | ||||
|             }, | ||||
|             match get_github_api::<GitCommit>("https://api.github.com/repos/dani-garcia/bitwarden_rs/commits/master") { | ||||
|                 Ok(mut c) => { | ||||
|                     c.sha.truncate(8); | ||||
|                     c.sha | ||||
|                 }, | ||||
|                 _ => "-".to_string() | ||||
|             }, | ||||
|             match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest") { | ||||
|                 Ok(r) => r.tag_name.trim_start_matches('v').to_string(), | ||||
|                 _ => "-".to_string() | ||||
|             }, | ||||
|         ) | ||||
|     } else { | ||||
|         ("-".to_string(), "-".to_string(), "-".to_string()) | ||||
|     }; | ||||
|      | ||||
|     // Run the date check as the last item right before filling the json. | ||||
|     // This should ensure that the time difference between the browser and the server is as minimal as possible. | ||||
|     let dt = Utc::now(); | ||||
|     let server_time = dt.format("%Y-%m-%d %H:%M:%S").to_string(); | ||||
|  | ||||
| @@ -385,7 +404,7 @@ fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> { | ||||
|         "web_vault_version": web_vault_version.version, | ||||
|         "latest_release": latest_release, | ||||
|         "latest_commit": latest_commit, | ||||
|         "latest_web_build": latest_web_build.replace("v", ""), | ||||
|         "latest_web_build": latest_web_build, | ||||
|     }); | ||||
|  | ||||
|     let text = AdminTemplateData::diagnostics(diagnostics_json).render()?; | ||||
|   | ||||
| @@ -130,6 +130,16 @@ impl Attachment { | ||||
|         result.unwrap_or(0) | ||||
|     } | ||||
|  | ||||
|     pub fn count_by_user(user_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         attachments::table | ||||
|             .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) | ||||
|             .filter(ciphers::user_uuid.eq(user_uuid)) | ||||
|             .count() | ||||
|             .first::<i64>(&**conn) | ||||
|             .ok() | ||||
|             .unwrap_or(0) | ||||
|     } | ||||
|  | ||||
|     pub fn size_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         let result: Option<i64> = attachments::table | ||||
|             .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) | ||||
| @@ -140,4 +150,14 @@ impl Attachment { | ||||
|  | ||||
|         result.unwrap_or(0) | ||||
|     } | ||||
|  | ||||
|     pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         attachments::table | ||||
|             .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) | ||||
|             .filter(ciphers::organization_uuid.eq(org_uuid)) | ||||
|             .count() | ||||
|             .first(&**conn) | ||||
|             .ok() | ||||
|             .unwrap_or(0) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -355,12 +355,13 @@ impl Cipher { | ||||
|         .load::<Self>(&**conn).expect("Error loading ciphers") | ||||
|     } | ||||
|  | ||||
|     pub fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> Option<i64> { | ||||
|     pub fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         ciphers::table | ||||
|         .filter(ciphers::user_uuid.eq(user_uuid)) | ||||
|         .count() | ||||
|         .first::<i64>(&**conn) | ||||
|         .ok() | ||||
|         .unwrap_or(0) | ||||
|     } | ||||
|  | ||||
|     pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
| @@ -369,6 +370,15 @@ impl Cipher { | ||||
|             .load::<Self>(&**conn).expect("Error loading ciphers") | ||||
|     } | ||||
|  | ||||
|     pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         ciphers::table | ||||
|             .filter(ciphers::organization_uuid.eq(org_uuid)) | ||||
|             .count() | ||||
|             .first::<i64>(&**conn) | ||||
|             .ok() | ||||
|             .unwrap_or(0) | ||||
|     } | ||||
|  | ||||
|     pub fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         folders_ciphers::table.inner_join(ciphers::table) | ||||
|             .filter(folders_ciphers::folder_uuid.eq(folder_uuid)) | ||||
|   | ||||
| @@ -437,6 +437,15 @@ impl UserOrganization { | ||||
|             .expect("Error loading user organizations") | ||||
|     } | ||||
|  | ||||
|     pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         users_organizations::table | ||||
|             .filter(users_organizations::org_uuid.eq(org_uuid)) | ||||
|             .count() | ||||
|             .first::<i64>(&**conn) | ||||
|             .ok() | ||||
|             .unwrap_or(0) | ||||
|     } | ||||
|  | ||||
|     pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec<Self> { | ||||
|         users_organizations::table | ||||
|             .filter(users_organizations::org_uuid.eq(org_uuid)) | ||||
|   | ||||
| @@ -9,24 +9,26 @@ | ||||
|                     <dt class="col-sm-5">Server Installed | ||||
|                         <span class="badge badge-success d-none" id="server-success" title="Latest version is installed.">Ok</span> | ||||
|                         <span class="badge badge-warning d-none" id="server-warning" title="There seems to be an update available.">Update</span> | ||||
|                         <span class="badge badge-danger d-none" id="server-failed" title="Unable to determine latest version.">Unknown</span> | ||||
|                     </dt> | ||||
|                     <dd class="col-sm-7"> | ||||
|                         <span id="server-installed">{{version}}</span> | ||||
|                     </dd> | ||||
|                     <dt class="col-sm-5">Server Latest</dt> | ||||
|                     <dt class="col-sm-5">Server Latest | ||||
|                         <span class="badge badge-danger d-none" id="server-failed" title="Unable to determine latest version.">Unknown</span> | ||||
|                     </dt> | ||||
|                     <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> | ||||
|                     <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> | ||||
|                         <span class="badge badge-danger d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span> | ||||
|                     </dt> | ||||
|                     <dd class="col-sm-7"> | ||||
|                         <span id="web-installed">{{diagnostics.web_vault_version}}</span> | ||||
|                     </dd> | ||||
|                     <dt class="col-sm-5">Web Latest</dt> | ||||
|                     <dt class="col-sm-5">Web Latest | ||||
|                         <span class="badge badge-danger d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span> | ||||
|                     </dt> | ||||
|                     <dd class="col-sm-7"> | ||||
|                         <span id="web-latest">{{diagnostics.latest_web_build}}</span> | ||||
|                     </dd> | ||||
|   | ||||
| @@ -2,24 +2,45 @@ | ||||
|     <div id="organizations-block" class="my-3 p-3 bg-white rounded shadow"> | ||||
|         <h6 class="border-bottom pb-2 mb-0">Organizations</h6> | ||||
|  | ||||
|         <div id="organizations-list"> | ||||
|             {{#each organizations}} | ||||
|             <div class="media pt-3"> | ||||
|                 <img class="mr-2 rounded identicon" data-src="{{Name}}_{{BillingEmail}}"> | ||||
|                 <div class="media-body pb-3 mb-0 small border-bottom"> | ||||
|                     <div class="row justify-content-between"> | ||||
|                         <div class="col"> | ||||
|         <div class="table-responsive-xl small"> | ||||
|             <table class="table table-sm table-striped table-hover"> | ||||
|                 <thead> | ||||
|                     <tr> | ||||
|                         <th style="width: 24px;" colspan="2">Organization</th> | ||||
|                         <th>Users</th> | ||||
|                         <th>Items</th> | ||||
|                         <th>Attachments</th> | ||||
|                     </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                     {{#each organizations}} | ||||
|                     <tr> | ||||
|                         <td><img class="rounded identicon" data-src="{{Id}}"></td> | ||||
|                         <td> | ||||
|                             <strong>{{Name}}</strong> | ||||
|                             {{#if Id}} | ||||
|                             <span class="badge badge-success ml-2">{{Id}}</span> | ||||
|                             <span class="mr-2">({{BillingEmail}})</span> | ||||
|                             <span class="d-block"> | ||||
|                                 <span class="badge badge-success">{{Id}}</span> | ||||
|                             </span> | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             <span class="d-block">{{user_count}}</span> | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             <span class="d-block">{{cipher_count}}</span> | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             <span class="d-block"><strong>Amount:</strong> {{attachment_count}}</span> | ||||
|                             {{#if attachment_count}} | ||||
|                             <span class="d-block"><strong>Size:</strong> {{attachment_size}}</span> | ||||
|                             {{/if}} | ||||
|                             <span class="d-block">{{BillingEmail}}</span> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             {{/each}} | ||||
|                         </td> | ||||
|                     </tr> | ||||
|                     {{/each}} | ||||
|                 </tbody> | ||||
|             </table> | ||||
|         </div> | ||||
|          | ||||
|     </div> | ||||
| </main> | ||||
|  | ||||
|   | ||||
| @@ -2,15 +2,14 @@ | ||||
|     <div id="users-block" class="my-3 p-3 bg-white rounded shadow"> | ||||
|         <h6 class="border-bottom pb-2 mb-0">Registered Users</h6> | ||||
|  | ||||
|  | ||||
|  | ||||
|         <div class="table-responsive-xl small"> | ||||
|             <table class="table table-sm table-striped table-hover"> | ||||
|                 <thead> | ||||
|                     <tr> | ||||
|                         <th style="width: 24px;">User</th> | ||||
|                         <th></th> | ||||
|                         <th style="width:90px; min-width: 90px;">Items</th> | ||||
|                         <th style="width:60px; min-width: 60px;">Items</th> | ||||
|                         <th>Attachments</th> | ||||
|                         <th style="min-width: 140px;">Organizations</th> | ||||
|                         <th style="width: 140px; min-width: 140px;">Actions</th> | ||||
|                     </tr> | ||||
| @@ -37,6 +36,12 @@ | ||||
|                         <td> | ||||
|                             <span class="d-block">{{cipher_count}}</span> | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             <span class="d-block"><strong>Amount:</strong> {{attachment_count}}</span> | ||||
|                             {{#if attachment_count}} | ||||
|                             <span class="d-block"><strong>Size:</strong> {{attachment_size}}</span> | ||||
|                             {{/if}} | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             {{#each Organizations}} | ||||
|                             <span class="badge badge-primary" data-orgtype="{{Type}}">{{Name}}</span> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user