vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 41e5f8af46091c8b3f2ecbef5b767dd3a74cf11d
parent fd9bc91a387b8779e64e0dc82a5ea39f82e227c9
Author: Zack Newman <zack@philomathiclife.com>
Date:   Sun,  3 Nov 2024 14:03:50 -0700

merge upstream

Diffstat:
MCargo.toml | 12++++++------
Msrc/api/core/ciphers.rs | 1-
Msrc/api/core/organizations.rs | 11++++++-----
Msrc/api/identity.rs | 10----------
Msrc/db/models/cipher.rs | 22+++++++++++++++++++++-
Msrc/db/models/collection.rs | 6+++---
Msrc/db/models/organization.rs | 45++++++++++++++++++++++++++++++++++++++-------
Msrc/db/models/user.rs | 1+
8 files changed, 75 insertions(+), 33 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -17,7 +17,7 @@ version = "3.0.0" maintenance = { status = "actively-developed" } [target.'cfg(target_os = "openbsd")'.dependencies] -priv_sep = { version = "2.0.0", default-features = false, features = ["openbsd"], optional = true } +priv_sep = { version = "2.1.0", default-features = false, features = ["openbsd"], optional = true } [dependencies] chrono = { version = "0.4.38", default-features = false, features = ["serde"] } @@ -25,19 +25,19 @@ data-encoding = { version = "2.6.0", default-features = false } diesel = { version = "2.2.4", default-features = false, features = ["32-column-tables", "chrono", "r2d2", "sqlite"] } jsonwebtoken = { version = "9.3.0", default-features = false, features = ["use_pem"] } libsqlite3-sys = { version = "0.30.1", default-features = false, features = ["bundled"] } -openssl = { version = "0.10.66", default-features = false } +openssl = { version = "0.10.68", default-features = false } paste = { version = "1.0.15", default-features = false } rand = { version = "0.8.5", default-features = false, features = ["small_rng"] } ring = { version = "0.17.8", default-features = false } rocket = { version = "0.5.1", default-features = false, features = ["json", "tls"] } semver = { version = "1.0.23", default-features = false } -serde = { version = "1.0.210", default-features = false } -serde_json = { version = "1.0.128", default-features = false } -tokio = { version = "1.40.0", default-features = false } +serde = { version = "1.0.214", default-features = false } +serde_json = { version = "1.0.132", default-features = false } +tokio = { version = "1.41.0", default-features = false } toml = { version = "0.8.19", default-features = false, features = ["parse"] } totp-lite = { version = "2.0.1", default-features = false } url = { version = "2.5.2", default-features = false } -uuid = { version = "1.10.0", default-features = false, features = ["v4"] } +uuid = { version = "1.11.0", default-features = false, features = ["v4"] } webauthn-rs = { version = "0.4.8", default-features = false, features = ["danger-allow-state-serialisation", "danger-user-presence-only-security-keys"] } [features] diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs @@ -146,7 +146,6 @@ async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> { "ciphers": ciphers_json, "domains": domains_json, "sends": Vec::<Value>::new(), - "unofficialServer": true, "object": "sync" })) } diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs @@ -298,11 +298,12 @@ async fn get_org_collections_details( }) .collect(); let groups = Vec::<Value>::new(); - let mut json_object = col.to_json(); + let mut json_object = col.to_json_details(&headers.user.uuid, None, &conn).await; json_object["assigned"] = json!(assigned); json_object["users"] = json!(users); json_object["groups"] = json!(groups); json_object["object"] = json!("collectionAccessDetails"); + json_object["unmanaged"] = json!(false); data.push(json_object); } Ok(Json(json!({ @@ -577,7 +578,9 @@ async fn get_org_collection_detail( .collect(); let assigned = Collection::can_access_collection(&user_org, &collection.uuid, &conn).await; - let mut json_object = collection.to_json(); + let mut json_object = collection + .to_json_details(&headers.user.uuid, None, &conn) + .await; json_object["assigned"] = json!(assigned); json_object["users"] = json!(users); json_object["groups"] = json!(groups); @@ -1130,9 +1133,7 @@ async fn post_org_import( let mut ciphers = Vec::new(); for cipher_data in data.ciphers { let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone()); - update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &conn, true) - .await - .ok(); + drop(update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &conn, true).await); ciphers.push(cipher); } // Assign the collections diff --git a/src/api/identity.rs b/src/api/identity.rs @@ -71,16 +71,7 @@ async fn _refresh_login(data: ConnectData, conn: &DbConn) -> JsonResult { "expires_in": expires_in, "token_type": "Bearer", "refresh_token": device.refresh_token, - "Key": user.akey, - "PrivateKey": user.private_key, - - "Kdf": user.client_kdf_type, - "KdfIterations": user.client_kdf_iter(), - "KdfMemory": user.client_kdf_memory(), - "KdfParallelism": user.client_kdf_parallelism(), - "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing "scope": scope, - "unofficialServer": true, }); Ok(Json(result)) } @@ -207,7 +198,6 @@ async fn _password_login( "ForcePasswordReset": false, "MasterPasswordPolicy": master_password_policy, "scope": scope, - "unofficialServer": true, "UserDecryptionOptions": { "HasMasterPassword": !user.password_hash.is_empty(), "Object": "userDecryptionOptions" diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs @@ -139,7 +139,27 @@ impl Cipher { .inspect_err(|e| warn!("Error parsing fields {e:?} for {}", self.uuid)) .ok() }) - .map_or(Value::Null, |d| d.into_iter().map(|da| da.data).collect()); + .map_or(Value::Null, |d| { + d.into_iter() + .map(|mut da| match da.data.get("type") { + None => { + da.data["type"] = json!(1i32); + } + Some(x) => { + if x.is_string() { + let type_num = x + .as_str() + .unwrap_or_else(|| { + unreachable!("there is a bug in Value::is_string") + }) + .parse::<u8>() + .unwrap_or(1); + da.data["type"] = json!(type_num); + } + } + }) + .collect() + }); let password_history_json = self .password_history .as_ref() diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs @@ -80,9 +80,9 @@ impl Collection { { match cipher_sync_data.user_organizations.get(&self.org_uuid) { // Only for Manager types Bitwarden returns true for the can_manage option - // Owners and Admins always have false, but they can manage all collections anyway + // Owners and Admins always have true Some(uo) if uo.has_full_access() => { - (false, false, uo.atype == UserOrgType::Manager) + (false, false, uo.atype >= UserOrgType::Manager) } Some(uo) => { // Only let a manager manage collections when the have full read/write access @@ -105,7 +105,7 @@ impl Collection { .await { Some(ou) if ou.has_full_access() => { - (false, false, ou.atype == UserOrgType::Manager) + (false, false, ou.atype >= UserOrgType::Manager) } Some(ou) => { let is_manager = ou.atype == UserOrgType::Manager; diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs @@ -196,7 +196,6 @@ impl Organization { "identifier": null, // not supported by us "name": self.name, "seats": null, - "maxAutoscaleSeats": null, "maxCollections": null, "maxStorageGb": i16::MAX, "use2fa": true, @@ -269,6 +268,17 @@ impl UserOrganization { } } + /// Return the status of the user in an unrevoked state + pub fn get_unrevoked_status(&self) -> i32 { + if self.status <= i32::from(UserOrgStatus::Revoked) { + return self + .status + .checked_add(ACTIVATE_REVOKE_DIFF) + .unwrap_or_else(|| panic!("overflow")); + } + self.status + } + pub fn set_external_id(&mut self, external_id: Option<String>) -> bool { //Check if external id is empty. We don't want to have //empty strings in the database @@ -366,7 +376,6 @@ impl UserOrganization { "identifier": null, // Not supported "name": org.name, "seats": null, - "maxAutoscaleSeats": null, "maxCollections": null, "usersGetPremium": true, "use2fa": true, @@ -402,7 +411,7 @@ impl UserOrganization { "familySponsorshipValidUntil": null, "familySponsorshipToDelete": null, "accessSecretsManager": false, - "limitCollectionCreationDeletion": true, + "limitCollectionCreationDeletion": false, // This should be set to true only when we can handle roles like createNewCollections "allowAdminAccessToAllCollectionItems": true, "flexibleCollections": false, "permissions": permissions, @@ -446,7 +455,7 @@ impl UserOrganization { .into_iter() .map(|c| { let (read_only, hide_passwords, can_manage) = if self.has_full_access() { - (false, false, self.atype == UserOrgType::Manager) + (false, false, self.atype >= UserOrgType::Manager) } else if let Some(cu) = cu.get(&c.uuid) { ( cu.read_only, @@ -468,13 +477,30 @@ impl UserOrganization { }) .collect() } else { - Vec::with_capacity(0) + Vec::new() }; - + let permissions = json!({ + // TODO: Add support for Custom User Roles + // See: https://bitwarden.com/help/article/user-types-access-control/#custom-role + "accessEventLogs": false, + "accessImportExport": false, + "accessReports": false, + "createNewCollections": false, + "editAnyCollection": false, + "deleteAnyCollection": false, + "editAssignedCollections": false, + "deleteAssignedCollections": false, + "manageGroups": false, + "managePolicies": false, + "manageSso": false, // Not supported + "manageUsers": false, + "manageResetPassword": false, + "manageScim": false // Not supported (Not AGPLv3 Licensed) + }); json!({ "id": self.uuid, "userId": self.user_uuid, - "name": user.name, + "name": if self.get_unrevoked_status() >= i32::from(UserOrgStatus::Accepted) { Some(user.name) } else { None }, "email": user.email, "externalId": self.external_id, "avatarColor": user.avatar_color, @@ -485,6 +511,11 @@ impl UserOrganization { "accessAll": self.access_all, "twoFactorEnabled": twofactor_enabled, "resetPasswordEnrolled": self.reset_password_key.is_some(), + "hasMasterPassword": !user.password_hash.is_empty(), + "permissions": permissions, + "ssoBound": false, // Not supported + "usesKeyConnector": false, // Not supported + "accessSecretsManager": false, // Not supported (Not AGPLv3 Licensed) "object": "organizationUserUserDetails", }) } diff --git a/src/db/models/user.rs b/src/db/models/user.rs @@ -292,6 +292,7 @@ impl User { "forcePasswordReset": false, "avatarColor": self.avatar_color, "usesKeyConnector": false, + "creationDate": util::format_date(&self.created_at), "object": "profile", }) }