Fix collection management and match some json output (#5095)

- Fixed collection management to be usable from the Password Manager UI
- Checked and brought in-to-sync with upstream several json responses
- Fixed a small issue with the `fields` response when it was empty

Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
Mathijs van Veluw 2024-10-18 20:37:32 +02:00 committed by GitHub
parent b7c254eb30
commit ae6ed0ece8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 16 additions and 27 deletions

View file

@ -150,7 +150,6 @@ async fn sync(data: SyncData, headers: Headers, mut conn: DbConn) -> Json<Value>
"ciphers": ciphers_json, "ciphers": ciphers_json,
"domains": domains_json, "domains": domains_json,
"sends": sends_json, "sends": sends_json,
"unofficialServer": true,
"object": "sync" "object": "sync"
})) }))
} }

View file

@ -363,6 +363,7 @@ async fn get_org_collections_details(org_id: &str, headers: ManagerHeadersLoose,
json_object["users"] = json!(users); json_object["users"] = json!(users);
json_object["groups"] = json!(groups); json_object["groups"] = json!(groups);
json_object["object"] = json!("collectionAccessDetails"); json_object["object"] = json!("collectionAccessDetails");
json_object["unmanaged"] = json!(false);
data.push(json_object) data.push(json_object)
} }

View file

@ -120,16 +120,8 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
"expires_in": expires_in, "expires_in": expires_in,
"token_type": "Bearer", "token_type": "Bearer",
"refresh_token": device.refresh_token, "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, "scope": scope,
"unofficialServer": true,
}); });
Ok(Json(result)) Ok(Json(result))
@ -342,7 +334,6 @@ async fn _password_login(
"MasterPasswordPolicy": master_password_policy, "MasterPasswordPolicy": master_password_policy,
"scope": scope, "scope": scope,
"unofficialServer": true,
"UserDecryptionOptions": { "UserDecryptionOptions": {
"HasMasterPassword": !user.password_hash.is_empty(), "HasMasterPassword": !user.password_hash.is_empty(),
"Object": "userDecryptionOptions" "Object": "userDecryptionOptions"
@ -461,9 +452,8 @@ async fn _user_api_key_login(
"KdfIterations": user.client_kdf_iter, "KdfIterations": user.client_kdf_iter,
"KdfMemory": user.client_kdf_memory, "KdfMemory": user.client_kdf_memory,
"KdfParallelism": user.client_kdf_parallelism, "KdfParallelism": user.client_kdf_parallelism,
"ResetMasterPassword": false, // TODO: Same as above "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
"scope": "api", "scope": "api",
"unofficialServer": true,
}); });
Ok(Json(result)) Ok(Json(result))
@ -495,7 +485,6 @@ async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &
"expires_in": 3600, "expires_in": 3600,
"token_type": "Bearer", "token_type": "Bearer",
"scope": "api.organization", "scope": "api.organization",
"unofficialServer": true,
}))) })))
} }

View file

@ -264,7 +264,7 @@ impl Cipher {
// NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream // NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream
// data_json should always contain the following keys with every atype // data_json should always contain the following keys with every atype
data_json["fields"] = json!([fields_json]); data_json["fields"] = json!(fields_json);
data_json["name"] = json!(self.name); data_json["name"] = json!(self.name);
data_json["notes"] = json!(self.notes); data_json["notes"] = json!(self.notes);
data_json["passwordHistory"] = Value::Array(password_history_json.clone()); data_json["passwordHistory"] = Value::Array(password_history_json.clone());

View file

@ -81,8 +81,8 @@ impl Collection {
let (read_only, hide_passwords, can_manage) = if let Some(cipher_sync_data) = cipher_sync_data { let (read_only, hide_passwords, can_manage) = if let Some(cipher_sync_data) = cipher_sync_data {
match cipher_sync_data.user_organizations.get(&self.org_uuid) { match cipher_sync_data.user_organizations.get(&self.org_uuid) {
// Only for Manager types Bitwarden returns true for the can_manage option // 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), Some(uo) if uo.has_full_access() => (false, false, uo.atype >= UserOrgType::Manager),
Some(uo) => { Some(uo) => {
// Only let a manager manage collections when the have full read/write access // Only let a manager manage collections when the have full read/write access
let is_manager = uo.atype == UserOrgType::Manager; let is_manager = uo.atype == UserOrgType::Manager;
@ -98,7 +98,7 @@ impl Collection {
} }
} else { } else {
match UserOrganization::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn).await { match UserOrganization::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn).await {
Some(ou) if ou.has_full_access() => (false, false, ou.atype == UserOrgType::Manager), Some(ou) if ou.has_full_access() => (false, false, ou.atype >= UserOrgType::Manager),
Some(ou) => { Some(ou) => {
let is_manager = ou.atype == UserOrgType::Manager; let is_manager = ou.atype == UserOrgType::Manager;
let read_only = !self.is_writable_by_user(user_uuid, conn).await; let read_only = !self.is_writable_by_user(user_uuid, conn).await;

View file

@ -82,7 +82,7 @@ impl Group {
"id": entry.collections_uuid, "id": entry.collections_uuid,
"readOnly": entry.read_only, "readOnly": entry.read_only,
"hidePasswords": entry.hide_passwords, "hidePasswords": entry.hide_passwords,
"manage": *user_org_type == UserOrgType::Manager && !entry.read_only && !entry.hide_passwords "manage": *user_org_type >= UserOrgType::Admin || (*user_org_type == UserOrgType::Manager && !entry.read_only && !entry.hide_passwords)
}) })
}) })
.collect(); .collect();

View file

@ -161,7 +161,6 @@ impl Organization {
"identifier": null, // not supported by us "identifier": null, // not supported by us
"name": self.name, "name": self.name,
"seats": null, "seats": null,
"maxAutoscaleSeats": null,
"maxCollections": null, "maxCollections": null,
"maxStorageGb": i16::MAX, // The value doesn't matter, we don't check server-side "maxStorageGb": i16::MAX, // The value doesn't matter, we don't check server-side
"use2fa": true, "use2fa": true,
@ -374,7 +373,6 @@ impl UserOrganization {
"identifier": null, // Not supported "identifier": null, // Not supported
"name": org.name, "name": org.name,
"seats": null, "seats": null,
"maxAutoscaleSeats": null,
"maxCollections": null, "maxCollections": null,
"usersGetPremium": true, "usersGetPremium": true,
"use2fa": true, "use2fa": true,
@ -411,7 +409,7 @@ impl UserOrganization {
"familySponsorshipValidUntil": null, "familySponsorshipValidUntil": null,
"familySponsorshipToDelete": null, "familySponsorshipToDelete": null,
"accessSecretsManager": false, "accessSecretsManager": false,
"limitCollectionCreationDeletion": true, "limitCollectionCreationDeletion": false, // This should be set to true only when we can handle roles like createNewCollections
"allowAdminAccessToAllCollectionItems": true, "allowAdminAccessToAllCollectionItems": true,
"flexibleCollections": false, "flexibleCollections": false,
@ -477,7 +475,7 @@ impl UserOrganization {
.into_iter() .into_iter()
.filter_map(|c| { .filter_map(|c| {
let (read_only, hide_passwords, can_manage) = if self.has_full_access() { 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) { } else if let Some(cu) = cu.get(&c.uuid) {
( (
cu.read_only, cu.read_only,

View file

@ -1,3 +1,4 @@
use crate::util::{format_date, get_uuid, retry};
use chrono::{NaiveDateTime, TimeDelta, Utc}; use chrono::{NaiveDateTime, TimeDelta, Utc};
use serde_json::Value; use serde_json::Value;
@ -90,7 +91,7 @@ impl User {
let email = email.to_lowercase(); let email = email.to_lowercase();
Self { Self {
uuid: crate::util::get_uuid(), uuid: get_uuid(),
enabled: true, enabled: true,
created_at: now, created_at: now,
updated_at: now, updated_at: now,
@ -107,7 +108,7 @@ impl User {
salt: crypto::get_random_bytes::<64>().to_vec(), salt: crypto::get_random_bytes::<64>().to_vec(),
password_iterations: CONFIG.password_iterations(), password_iterations: CONFIG.password_iterations(),
security_stamp: crate::util::get_uuid(), security_stamp: get_uuid(),
stamp_exception: None, stamp_exception: None,
password_hint: None, password_hint: None,
@ -188,7 +189,7 @@ impl User {
} }
pub fn reset_security_stamp(&mut self) { pub fn reset_security_stamp(&mut self) {
self.security_stamp = crate::util::get_uuid(); self.security_stamp = get_uuid();
} }
/// Set the stamp_exception to only allow a subsequent request matching a specific route using the current security-stamp. /// Set the stamp_exception to only allow a subsequent request matching a specific route using the current security-stamp.
@ -259,6 +260,7 @@ impl User {
"forcePasswordReset": false, "forcePasswordReset": false,
"avatarColor": self.avatar_color, "avatarColor": self.avatar_color,
"usesKeyConnector": false, "usesKeyConnector": false,
"creationDate": format_date(&self.created_at),
"object": "profile", "object": "profile",
}) })
} }
@ -340,7 +342,7 @@ impl User {
let updated_at = Utc::now().naive_utc(); let updated_at = Utc::now().naive_utc();
db_run! {conn: { db_run! {conn: {
crate::util::retry(|| { retry(|| {
diesel::update(users::table) diesel::update(users::table)
.set(users::updated_at.eq(updated_at)) .set(users::updated_at.eq(updated_at))
.execute(conn) .execute(conn)
@ -357,7 +359,7 @@ impl User {
async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult { async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &mut DbConn) -> EmptyResult {
db_run! {conn: { db_run! {conn: {
crate::util::retry(|| { retry(|| {
diesel::update(users::table.filter(users::uuid.eq(uuid))) diesel::update(users::table.filter(users::uuid.eq(uuid)))
.set(users::updated_at.eq(date)) .set(users::updated_at.eq(date))
.execute(conn) .execute(conn)