mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-03-23 02:09:20 -07:00
Sync with Upstream (#5798)
* WIP Sync with Upstream WIP on syncing API Responses with upstream. This to prevent issues with new clients, and find possible current issues like members, collections, groups etc.. Signed-off-by: BlackDex <black.dex@gmail.com> * More API Response fixes - Some 2fa checks - Some org checks - Reconfigured the experimental flags and noted which are deprecated Also removed some hard-coded defaults. - Updated crates Signed-off-by: BlackDex <black.dex@gmail.com> * Add avatar color to emergency access api Signed-off-by: BlackDex <black.dex@gmail.com> * Fix spelling and some crate updates Signed-off-by: BlackDex <black.dex@gmail.com> * Use PushId and always generate the PushId Signed-off-by: BlackDex <black.dex@gmail.com> * Fix clippy lints Signed-off-by: BlackDex <black.dex@gmail.com> * Fix several Push issues and API's Signed-off-by: BlackDex <black.dex@gmail.com> * Check if push_uuid is empty and generate when needed Signed-off-by: BlackDex <black.dex@gmail.com> * Updated some comments and removed old export format Signed-off-by: BlackDex <black.dex@gmail.com> * cargo update Signed-off-by: BlackDex <black.dex@gmail.com> * Fix bulk edit Fixes #5737 Signed-off-by: BlackDex <black.dex@gmail.com> * Send an email when an account exists already When you want to change your email address into an account which already exists, upstream sends an email to the existing account. Lets do the same. Kinda fixes #5630 Signed-off-by: BlackDex <black.dex@gmail.com> * Update 2fa removal/revoke email Signed-off-by: BlackDex <black.dex@gmail.com> * Allow col managers to import This commit adds functionality to allow users with manage access to a collection, or managers with all access to import into an organization. Fixes #5592 Signed-off-by: BlackDex <black.dex@gmail.com> * Filter deprected flags and only return active flags Signed-off-by: BlackDex <black.dex@gmail.com> * Fix grammer Signed-off-by: BlackDex <black.dex@gmail.com> * Rename Small to Compact Signed-off-by: BlackDex <black.dex@gmail.com> * Rebase with upstream and fix conflicts Signed-off-by: BlackDex <black.dex@gmail.com> --------- Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
committed by
GitHub
parent
3a44dc963b
commit
ef7835d1b0
@@ -336,7 +336,6 @@ async fn profile(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ProfileData {
|
||||
// culture: String, // Ignored, always use en-US
|
||||
// masterPasswordHint: Option<String>, // Ignored, has been moved to ChangePassData
|
||||
name: String,
|
||||
}
|
||||
|
||||
@@ -462,7 +461,7 @@ async fn post_password(data: Json<ChangePassData>, headers: Headers, mut conn: D
|
||||
// Prevent logging out the client where the user requested this endpoint from.
|
||||
// If you do logout the user it will causes issues at the client side.
|
||||
// Adding the device uuid will prevent this.
|
||||
nt.send_logout(&user, Some(headers.device.uuid.clone())).await;
|
||||
nt.send_logout(&user, Some(headers.device.uuid.clone()), &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -522,7 +521,7 @@ async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, mut conn: DbConn,
|
||||
user.set_password(&data.new_master_password_hash, Some(data.key), true, None);
|
||||
let save_result = user.save(&mut conn).await;
|
||||
|
||||
nt.send_logout(&user, Some(headers.device.uuid.clone())).await;
|
||||
nt.send_logout(&user, Some(headers.device.uuid.clone()), &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -734,7 +733,7 @@ async fn post_rotatekey(data: Json<KeyData>, headers: Headers, mut conn: DbConn,
|
||||
// Prevent logging out the client where the user requested this endpoint from.
|
||||
// If you do logout the user it will causes issues at the client side.
|
||||
// Adding the device uuid will prevent this.
|
||||
nt.send_logout(&user, Some(headers.device.uuid.clone())).await;
|
||||
nt.send_logout(&user, Some(headers.device.uuid.clone()), &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -750,7 +749,7 @@ async fn post_sstamp(data: Json<PasswordOrOtpData>, headers: Headers, mut conn:
|
||||
user.reset_security_stamp();
|
||||
let save_result = user.save(&mut conn).await;
|
||||
|
||||
nt.send_logout(&user, None).await;
|
||||
nt.send_logout(&user, None, &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -776,6 +775,11 @@ async fn post_email_token(data: Json<EmailTokenData>, headers: Headers, mut conn
|
||||
}
|
||||
|
||||
if User::find_by_mail(&data.new_email, &mut conn).await.is_some() {
|
||||
if CONFIG.mail_enabled() {
|
||||
if let Err(e) = mail::send_change_email_existing(&data.new_email, &user.email).await {
|
||||
error!("Error sending change-email-existing email: {e:#?}");
|
||||
}
|
||||
}
|
||||
err!("Email already in use");
|
||||
}
|
||||
|
||||
@@ -858,7 +862,7 @@ async fn post_email(data: Json<ChangeEmailData>, headers: Headers, mut conn: DbC
|
||||
|
||||
let save_result = user.save(&mut conn).await;
|
||||
|
||||
nt.send_logout(&user, None).await;
|
||||
nt.send_logout(&user, None, &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -1056,7 +1060,7 @@ pub async fn _prelogin(data: Json<PreloginData>, mut conn: DbConn) -> Json<Value
|
||||
}))
|
||||
}
|
||||
|
||||
// https://github.com/bitwarden/server/blob/master/src/Api/Models/Request/Accounts/SecretVerificationRequestModel.cs
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Auth/Models/Request/Accounts/SecretVerificationRequestModel.cs
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SecretVerificationRequest {
|
||||
@@ -1197,19 +1201,14 @@ async fn put_device_token(
|
||||
err!(format!("Error: device {device_id} should be present before a token can be assigned"))
|
||||
};
|
||||
|
||||
// if the device already has been registered
|
||||
if device.is_registered() {
|
||||
// check if the new token is the same as the registered token
|
||||
if device.push_token.is_some() && device.push_token.unwrap() == token.clone() {
|
||||
debug!("Device {device_id} is already registered and token is the same");
|
||||
return Ok(());
|
||||
} else {
|
||||
// Try to unregister already registered device
|
||||
unregister_push_device(device.push_uuid).await.ok();
|
||||
}
|
||||
// clear the push_uuid
|
||||
device.push_uuid = None;
|
||||
// Check if the new token is the same as the registered token
|
||||
// Although upstream seems to always register a device on login, we do not.
|
||||
// Unless this causes issues, lets keep it this way, else we might need to also register on every login.
|
||||
if device.push_token.as_ref() == Some(&token) {
|
||||
debug!("Device {device_id} for user {} is already registered and token is identical", headers.user.uuid);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
device.push_token = Some(token);
|
||||
if let Err(e) = device.save(&mut conn).await {
|
||||
err!(format!("An error occurred while trying to save the device push token: {e}"));
|
||||
@@ -1223,16 +1222,19 @@ async fn put_device_token(
|
||||
#[put("/devices/identifier/<device_id>/clear-token")]
|
||||
async fn put_clear_device_token(device_id: DeviceId, mut conn: DbConn) -> EmptyResult {
|
||||
// This only clears push token
|
||||
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
|
||||
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Controllers/DevicesController.cs#L215
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/Services/Implementations/DeviceService.cs#L37
|
||||
// This is somehow not implemented in any app, added it in case it is required
|
||||
// 2025: Also, it looks like it only clears the first found device upstream, which is probably faulty.
|
||||
// This because currently multiple accounts could be on the same device/app and that would cause issues.
|
||||
// Vaultwarden removes the push-token for all devices, but this probably means we should also unregister all these devices.
|
||||
if !CONFIG.push_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(device) = Device::find_by_uuid(&device_id, &mut conn).await {
|
||||
Device::clear_push_token_by_uuid(&device_id, &mut conn).await?;
|
||||
unregister_push_device(device.push_uuid).await?;
|
||||
unregister_push_device(&device.push_uuid).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1270,10 +1272,10 @@ async fn post_auth_request(
|
||||
};
|
||||
|
||||
// Validate device uuid and type
|
||||
match Device::find_by_uuid_and_user(&data.device_identifier, &user.uuid, &mut conn).await {
|
||||
Some(device) if device.atype == client_headers.device_type => {}
|
||||
let device = match Device::find_by_uuid_and_user(&data.device_identifier, &user.uuid, &mut conn).await {
|
||||
Some(device) if device.atype == client_headers.device_type => device,
|
||||
_ => err!("AuthRequest doesn't exist", "Device verification failed"),
|
||||
}
|
||||
};
|
||||
|
||||
let mut auth_request = AuthRequest::new(
|
||||
user.uuid.clone(),
|
||||
@@ -1285,7 +1287,7 @@ async fn post_auth_request(
|
||||
);
|
||||
auth_request.save(&mut conn).await?;
|
||||
|
||||
nt.send_auth_request(&user.uuid, &auth_request.uuid, &data.device_identifier, &mut conn).await;
|
||||
nt.send_auth_request(&user.uuid, &auth_request.uuid, &device, &mut conn).await;
|
||||
|
||||
log_user_event(
|
||||
EventType::UserRequestedDeviceApproval as i32,
|
||||
@@ -1360,6 +1362,10 @@ async fn put_auth_request(
|
||||
err!("AuthRequest doesn't exist", "Record not found or user uuid does not match")
|
||||
};
|
||||
|
||||
if headers.device.uuid != data.device_identifier {
|
||||
err!("AuthRequest doesn't exist", "Device verification failed")
|
||||
}
|
||||
|
||||
if auth_request.approved.is_some() {
|
||||
err!("An authentication request with the same device already exists")
|
||||
}
|
||||
@@ -1376,7 +1382,7 @@ async fn put_auth_request(
|
||||
auth_request.save(&mut conn).await?;
|
||||
|
||||
ant.send_auth_response(&auth_request.user_uuid, &auth_request.uuid).await;
|
||||
nt.send_auth_response(&auth_request.user_uuid, &auth_request.uuid, &data.device_identifier, &mut conn).await;
|
||||
nt.send_auth_response(&auth_request.user_uuid, &auth_request.uuid, &headers.device, &mut conn).await;
|
||||
|
||||
log_user_event(
|
||||
EventType::OrganizationUserApprovedAuthRequest as i32,
|
||||
|
||||
@@ -535,7 +535,7 @@ pub async fn update_cipher_from_data(
|
||||
ut,
|
||||
cipher,
|
||||
&cipher.update_users_revision(conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
shared_to_collections,
|
||||
conn,
|
||||
)
|
||||
@@ -612,7 +612,7 @@ async fn post_ciphers_import(
|
||||
|
||||
let mut user = headers.user;
|
||||
user.update_revision(&mut conn).await?;
|
||||
nt.send_user_update(UpdateType::SyncVault, &user).await;
|
||||
nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &mut conn).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -808,7 +808,7 @@ async fn post_collections_update(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
&cipher,
|
||||
&cipher.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
Some(Vec::from_iter(posted_collections)),
|
||||
&mut conn,
|
||||
)
|
||||
@@ -885,7 +885,7 @@ async fn post_collections_admin(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
&cipher,
|
||||
&cipher.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
Some(Vec::from_iter(posted_collections)),
|
||||
&mut conn,
|
||||
)
|
||||
@@ -1281,7 +1281,7 @@ async fn save_attachment(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
&cipher,
|
||||
&cipher.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
None,
|
||||
&mut conn,
|
||||
)
|
||||
@@ -1582,7 +1582,7 @@ async fn move_cipher_selected(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
&cipher,
|
||||
std::slice::from_ref(&user_id),
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
None,
|
||||
&mut conn,
|
||||
)
|
||||
@@ -1629,7 +1629,7 @@ async fn delete_all(
|
||||
Some(member) => {
|
||||
if member.atype == MembershipType::Owner {
|
||||
Cipher::delete_all_by_organization(&org_data.org_id, &mut conn).await?;
|
||||
nt.send_user_update(UpdateType::SyncVault, &user).await;
|
||||
nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &mut conn).await;
|
||||
|
||||
log_event(
|
||||
EventType::OrganizationPurgedVault as i32,
|
||||
@@ -1662,7 +1662,7 @@ async fn delete_all(
|
||||
}
|
||||
|
||||
user.update_revision(&mut conn).await?;
|
||||
nt.send_user_update(UpdateType::SyncVault, &user).await;
|
||||
nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &mut conn).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1691,7 +1691,7 @@ async fn _delete_cipher_by_uuid(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
&cipher,
|
||||
&cipher.update_users_revision(conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
None,
|
||||
conn,
|
||||
)
|
||||
@@ -1702,7 +1702,7 @@ async fn _delete_cipher_by_uuid(
|
||||
UpdateType::SyncCipherDelete,
|
||||
&cipher,
|
||||
&cipher.update_users_revision(conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
None,
|
||||
conn,
|
||||
)
|
||||
@@ -1767,7 +1767,7 @@ async fn _restore_cipher_by_uuid(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
&cipher,
|
||||
&cipher.update_users_revision(conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
None,
|
||||
conn,
|
||||
)
|
||||
@@ -1841,7 +1841,7 @@ async fn _delete_cipher_attachment_by_id(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
&cipher,
|
||||
&cipher.update_users_revision(conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
None,
|
||||
conn,
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ struct EventRange {
|
||||
continuation_token: Option<String>,
|
||||
}
|
||||
|
||||
// Upstream: https://github.com/bitwarden/server/blob/9ecf69d9cabce732cf2c57976dd9afa5728578fb/src/Api/Controllers/EventsController.cs#LL84C35-L84C41
|
||||
// Upstream: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Controllers/EventsController.cs#L87
|
||||
#[get("/organizations/<org_id>/events?<data..>")]
|
||||
async fn get_org_events(
|
||||
org_id: OrganizationId,
|
||||
@@ -169,8 +169,8 @@ struct EventCollection {
|
||||
}
|
||||
|
||||
// Upstream:
|
||||
// https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Events/Controllers/CollectController.cs
|
||||
// https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Services/Implementations/EventService.cs
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Events/Controllers/CollectController.cs
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Services/Implementations/EventService.cs
|
||||
#[post("/collect", format = "application/json", data = "<data>")]
|
||||
async fn post_events_collect(data: Json<Vec<EventCollection>>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
if !CONFIG.org_events_enabled() {
|
||||
|
||||
@@ -45,7 +45,7 @@ async fn post_folders(data: Json<FolderData>, headers: Headers, mut conn: DbConn
|
||||
let mut folder = Folder::new(headers.user.uuid, data.name);
|
||||
|
||||
folder.save(&mut conn).await?;
|
||||
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid, &mut conn).await;
|
||||
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device, &mut conn).await;
|
||||
|
||||
Ok(Json(folder.to_json()))
|
||||
}
|
||||
@@ -78,7 +78,7 @@ async fn put_folder(
|
||||
folder.name = data.name;
|
||||
|
||||
folder.save(&mut conn).await?;
|
||||
nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid, &mut conn).await;
|
||||
nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device, &mut conn).await;
|
||||
|
||||
Ok(Json(folder.to_json()))
|
||||
}
|
||||
@@ -97,6 +97,6 @@ async fn delete_folder(folder_id: FolderId, headers: Headers, mut conn: DbConn,
|
||||
// Delete the actual folder entry
|
||||
folder.delete(&mut conn).await?;
|
||||
|
||||
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid, &mut conn).await;
|
||||
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device, &mut conn).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ async fn post_eq_domains(
|
||||
|
||||
user.save(&mut conn).await?;
|
||||
|
||||
nt.send_user_update(UpdateType::SyncSettings, &user).await;
|
||||
nt.send_user_update(UpdateType::SyncSettings, &user, &headers.device.push_uuid, &mut conn).await;
|
||||
|
||||
Ok(Json(json!({})))
|
||||
}
|
||||
@@ -199,12 +199,14 @@ fn get_api_webauthn(_headers: Headers) -> Json<Value> {
|
||||
#[get("/config")]
|
||||
fn config() -> Json<Value> {
|
||||
let domain = crate::CONFIG.domain();
|
||||
// Official available feature flags can be found here:
|
||||
// Server (v2025.5.0): https://github.com/bitwarden/server/blob/4a7db112a0952c6df8bacf36c317e9c4e58c3651/src/Core/Constants.cs#L102
|
||||
// Client (v2025.5.0): https://github.com/bitwarden/clients/blob/9df8a3cc50ed45f52513e62c23fcc8a4b745f078/libs/common/src/enums/feature-flag.enum.ts#L10
|
||||
// Android (v2025.4.0): https://github.com/bitwarden/android/blob/bee09de972c3870de0d54a0067996be473ec55c7/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt#L27
|
||||
// iOS (v2025.4.0): https://github.com/bitwarden/ios/blob/956e05db67344c912e3a1b8cb2609165d67da1c9/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7
|
||||
let mut feature_states =
|
||||
parse_experimental_client_feature_flags(&crate::CONFIG.experimental_client_feature_flags());
|
||||
// Force the new key rotation feature
|
||||
feature_states.insert("key-rotation-improvements".to_string(), true);
|
||||
feature_states.insert("flexible-collections-v-1".to_string(), false);
|
||||
|
||||
feature_states.insert("duo-redirect".to_string(), true);
|
||||
feature_states.insert("email-verification".to_string(), true);
|
||||
feature_states.insert("unauth-ui-refresh".to_string(), true);
|
||||
|
||||
@@ -214,7 +216,7 @@ fn config() -> Json<Value> {
|
||||
// We should make sure that we keep this updated when we support the new server features
|
||||
// Version history:
|
||||
// - Individual cipher key encryption: 2024.2.0
|
||||
"version": "2025.1.0",
|
||||
"version": "2025.4.0",
|
||||
"gitHash": option_env!("GIT_REV"),
|
||||
"server": {
|
||||
"name": "Vaultwarden",
|
||||
@@ -229,6 +231,12 @@ fn config() -> Json<Value> {
|
||||
"identity": format!("{domain}/identity"),
|
||||
"notifications": format!("{domain}/notifications"),
|
||||
"sso": "",
|
||||
"cloudRegion": null,
|
||||
},
|
||||
// Bitwarden uses this for the self-hosted servers to indicate the default push technology
|
||||
"push": {
|
||||
"pushTechnology": 0,
|
||||
"vapidPublicKey": null
|
||||
},
|
||||
"featureStates": feature_states,
|
||||
"object": "config",
|
||||
|
||||
@@ -374,6 +374,21 @@ async fn get_org_collections_details(
|
||||
|| (CONFIG.org_groups_enabled()
|
||||
&& GroupUser::has_full_access_by_member(&org_id, &member.uuid, &mut conn).await);
|
||||
|
||||
// Get all admins, owners and managers who can manage/access all
|
||||
// Those are currently not listed in the col_users but need to be listed too.
|
||||
let manage_all_members: Vec<Value> = Membership::find_confirmed_and_manage_all_by_org(&org_id, &mut conn)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|member| {
|
||||
json!({
|
||||
"id": member.uuid,
|
||||
"readOnly": false,
|
||||
"hidePasswords": false,
|
||||
"manage": true,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
for col in Collection::find_by_organization(&org_id, &mut conn).await {
|
||||
// check whether the current user has access to the given collection
|
||||
let assigned = has_full_access_to_org
|
||||
@@ -382,7 +397,7 @@ async fn get_org_collections_details(
|
||||
&& GroupUser::has_access_to_collection_by_member(&col.uuid, &member.uuid, &mut conn).await);
|
||||
|
||||
// get the users assigned directly to the given collection
|
||||
let users: Vec<Value> = col_users
|
||||
let mut users: Vec<Value> = col_users
|
||||
.iter()
|
||||
.filter(|collection_member| collection_member.collection_uuid == col.uuid)
|
||||
.map(|collection_member| {
|
||||
@@ -391,6 +406,7 @@ async fn get_org_collections_details(
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
users.extend_from_slice(&manage_all_members);
|
||||
|
||||
// get the group details for the given collection
|
||||
let groups: Vec<Value> = if CONFIG.org_groups_enabled() {
|
||||
@@ -681,6 +697,9 @@ async fn _delete_organization_collection(
|
||||
headers: &ManagerHeaders,
|
||||
conn: &mut DbConn,
|
||||
) -> EmptyResult {
|
||||
if org_id != &headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
let Some(collection) = Collection::find_by_uuid_and_org(col_id, org_id, conn).await else {
|
||||
err!("Collection not found", "Collection does not exist or does not belong to this organization")
|
||||
};
|
||||
@@ -893,7 +912,7 @@ struct OrgIdData {
|
||||
|
||||
#[get("/ciphers/organization-details?<data..>")]
|
||||
async fn get_org_details(data: OrgIdData, headers: OrgMemberHeaders, mut conn: DbConn) -> JsonResult {
|
||||
if data.organization_id != headers.org_id {
|
||||
if data.organization_id != headers.membership.org_uuid {
|
||||
err_code!("Resource not found.", "Organization id's do not match", rocket::http::Status::NotFound.code);
|
||||
}
|
||||
|
||||
@@ -1180,6 +1199,9 @@ async fn reinvite_member(
|
||||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
if org_id != headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
_reinvite_member(&org_id, &member_id, &headers.user.email, &mut conn).await
|
||||
}
|
||||
|
||||
@@ -1397,6 +1419,9 @@ async fn _confirm_invite(
|
||||
conn: &mut DbConn,
|
||||
nt: &Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
if org_id != &headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
if key.is_empty() || member_id.is_empty() {
|
||||
err!("Key or UserId is not set, unable to process request");
|
||||
}
|
||||
@@ -1460,7 +1485,7 @@ async fn _confirm_invite(
|
||||
let save_result = member_to_confirm.save(conn).await;
|
||||
|
||||
if let Some(user) = User::find_by_uuid(&member_to_confirm.user_uuid, conn).await {
|
||||
nt.send_user_update(UpdateType::SyncOrgKeys, &user).await;
|
||||
nt.send_user_update(UpdateType::SyncOrgKeys, &user, &headers.device.push_uuid, conn).await;
|
||||
}
|
||||
|
||||
save_result
|
||||
@@ -1719,6 +1744,9 @@ async fn _delete_member(
|
||||
conn: &mut DbConn,
|
||||
nt: &Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
if org_id != &headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
let Some(member_to_delete) = Membership::find_by_uuid_and_org(member_id, org_id, conn).await else {
|
||||
err!("User to delete isn't member of the organization")
|
||||
};
|
||||
@@ -1747,7 +1775,7 @@ async fn _delete_member(
|
||||
.await;
|
||||
|
||||
if let Some(user) = User::find_by_uuid(&member_to_delete.user_uuid, conn).await {
|
||||
nt.send_user_update(UpdateType::SyncOrgKeys, &user).await;
|
||||
nt.send_user_update(UpdateType::SyncOrgKeys, &user, &headers.device.push_uuid, conn).await;
|
||||
}
|
||||
|
||||
member_to_delete.delete(conn).await
|
||||
@@ -1813,16 +1841,20 @@ struct RelationsData {
|
||||
value: usize,
|
||||
}
|
||||
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Tools/Controllers/ImportCiphersController.cs#L62
|
||||
#[post("/ciphers/import-organization?<query..>", data = "<data>")]
|
||||
async fn post_org_import(
|
||||
query: OrgIdData,
|
||||
data: Json<ImportData>,
|
||||
headers: AdminHeaders,
|
||||
headers: OrgMemberHeaders,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
let data: ImportData = data.into_inner();
|
||||
let org_id = query.organization_id;
|
||||
if org_id != headers.membership.org_uuid {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
let data: ImportData = data.into_inner();
|
||||
|
||||
// Validate the import before continuing
|
||||
// Bitwarden does not process the import if there is one item invalid.
|
||||
@@ -1835,8 +1867,20 @@ async fn post_org_import(
|
||||
let mut collections: Vec<CollectionId> = Vec::with_capacity(data.collections.len());
|
||||
for col in data.collections {
|
||||
let collection_uuid = if existing_collections.contains(&col.id) {
|
||||
col.id.unwrap()
|
||||
let col_id = col.id.unwrap();
|
||||
// When not an Owner or Admin, check if the member is allowed to access the collection.
|
||||
if headers.membership.atype < MembershipType::Admin
|
||||
&& !Collection::can_access_collection(&headers.membership, &col_id, &mut conn).await
|
||||
{
|
||||
err!(Compact, "The current user isn't allowed to manage this collection")
|
||||
}
|
||||
col_id
|
||||
} else {
|
||||
// We do not allow users or managers which can not manage all collections to create new collections
|
||||
// If there is any collection other than an existing import collection, abort the import.
|
||||
if headers.membership.atype <= MembershipType::Manager && !headers.membership.has_full_access() {
|
||||
err!(Compact, "The current user isn't allowed to create new collections")
|
||||
}
|
||||
let new_collection = Collection::new(org_id.clone(), col.name, col.external_id);
|
||||
new_collection.save(&mut conn).await?;
|
||||
new_collection.uuid
|
||||
@@ -1859,7 +1903,17 @@ async fn post_org_import(
|
||||
// Always clear folder_id's via an organization import
|
||||
cipher_data.folder_id = None;
|
||||
let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone());
|
||||
update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &mut conn, &nt, UpdateType::None).await.ok();
|
||||
update_cipher_from_data(
|
||||
&mut cipher,
|
||||
cipher_data,
|
||||
&headers,
|
||||
Some(collections.clone()),
|
||||
&mut conn,
|
||||
&nt,
|
||||
UpdateType::None,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
ciphers.push(cipher.uuid);
|
||||
}
|
||||
|
||||
@@ -1890,12 +1944,6 @@ struct BulkCollectionsData {
|
||||
async fn post_bulk_collections(data: Json<BulkCollectionsData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
let data: BulkCollectionsData = data.into_inner();
|
||||
|
||||
// This feature does not seem to be active on all the clients
|
||||
// To prevent future issues, add a check to block a call when this is set to true
|
||||
if data.remove_collections {
|
||||
err!("Bulk removing of collections is not yet implemented")
|
||||
}
|
||||
|
||||
// Get all the collection available to the user in one query
|
||||
// Also filter based upon the provided collections
|
||||
let user_collections: HashMap<CollectionId, Collection> =
|
||||
@@ -1924,8 +1972,16 @@ async fn post_bulk_collections(data: Json<BulkCollectionsData>, headers: Headers
|
||||
// Do not abort the operation just ignore it, it could be a cipher was just deleted for example
|
||||
if let Some(cipher) = Cipher::find_by_uuid_and_org(cipher_id, &data.organization_id, &mut conn).await {
|
||||
if cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await {
|
||||
for collection in &data.collection_ids {
|
||||
CollectionCipher::save(&cipher.uuid, collection, &mut conn).await?;
|
||||
// When selecting a specific collection from the left filter list, and use the bulk option, you can remove an item from that collection
|
||||
// In these cases the client will call this endpoint twice, once for adding the new collections and a second for deleting.
|
||||
if data.remove_collections {
|
||||
for collection in &data.collection_ids {
|
||||
CollectionCipher::delete(&cipher.uuid, collection, &mut conn).await?;
|
||||
}
|
||||
} else {
|
||||
for collection in &data.collection_ids {
|
||||
CollectionCipher::save(&cipher.uuid, collection, &mut conn).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2402,6 +2458,9 @@ async fn _revoke_member(
|
||||
headers: &AdminHeaders,
|
||||
conn: &mut DbConn,
|
||||
) -> EmptyResult {
|
||||
if org_id != &headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
match Membership::find_by_uuid_and_org(member_id, org_id, conn).await {
|
||||
Some(mut member) if member.status > MembershipStatus::Revoked as i32 => {
|
||||
if member.user_uuid == headers.user.uuid {
|
||||
@@ -2509,6 +2568,9 @@ async fn _restore_member(
|
||||
headers: &AdminHeaders,
|
||||
conn: &mut DbConn,
|
||||
) -> EmptyResult {
|
||||
if org_id != &headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
match Membership::find_by_uuid_and_org(member_id, org_id, conn).await {
|
||||
Some(mut member) if member.status < MembershipStatus::Accepted as i32 => {
|
||||
if member.user_uuid == headers.user.uuid {
|
||||
@@ -2556,18 +2618,27 @@ async fn _restore_member(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[get("/organizations/<org_id>/groups")]
|
||||
async fn get_groups(org_id: OrganizationId, headers: ManagerHeadersLoose, mut conn: DbConn) -> JsonResult {
|
||||
async fn get_groups_data(
|
||||
details: bool,
|
||||
org_id: OrganizationId,
|
||||
headers: ManagerHeadersLoose,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
if org_id != headers.membership.org_uuid {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
let groups: Vec<Value> = if CONFIG.org_groups_enabled() {
|
||||
// Group::find_by_organization(&org_id, &mut conn).await.iter().map(Group::to_json).collect::<Value>()
|
||||
let groups = Group::find_by_organization(&org_id, &mut conn).await;
|
||||
let mut groups_json = Vec::with_capacity(groups.len());
|
||||
|
||||
for g in groups {
|
||||
groups_json.push(g.to_json_details(&mut conn).await)
|
||||
if details {
|
||||
for g in groups {
|
||||
groups_json.push(g.to_json_details(&mut conn).await)
|
||||
}
|
||||
} else {
|
||||
for g in groups {
|
||||
groups_json.push(g.to_json())
|
||||
}
|
||||
}
|
||||
groups_json
|
||||
} else {
|
||||
@@ -2583,9 +2654,14 @@ async fn get_groups(org_id: OrganizationId, headers: ManagerHeadersLoose, mut co
|
||||
})))
|
||||
}
|
||||
|
||||
#[get("/organizations/<org_id>/groups")]
|
||||
async fn get_groups(org_id: OrganizationId, headers: ManagerHeadersLoose, conn: DbConn) -> JsonResult {
|
||||
get_groups_data(false, org_id, headers, conn).await
|
||||
}
|
||||
|
||||
#[get("/organizations/<org_id>/groups/details", rank = 1)]
|
||||
async fn get_groups_details(org_id: OrganizationId, headers: ManagerHeadersLoose, conn: DbConn) -> JsonResult {
|
||||
get_groups(org_id, headers, conn).await
|
||||
get_groups_data(true, org_id, headers, conn).await
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -2647,6 +2723,9 @@ async fn post_groups(
|
||||
data: Json<GroupRequest>,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
if org_id != headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
@@ -2676,6 +2755,9 @@ async fn put_group(
|
||||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
if org_id != headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
@@ -2740,7 +2822,8 @@ async fn add_update_group(
|
||||
"organizationId": group.organizations_uuid,
|
||||
"name": group.name,
|
||||
"accessAll": group.access_all,
|
||||
"externalId": group.external_id
|
||||
"externalId": group.external_id,
|
||||
"object": "group"
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -2791,6 +2874,9 @@ async fn _delete_group(
|
||||
headers: &AdminHeaders,
|
||||
conn: &mut DbConn,
|
||||
) -> EmptyResult {
|
||||
if org_id != &headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
@@ -2820,6 +2906,9 @@ async fn bulk_delete_groups(
|
||||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
if org_id != headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
@@ -2883,6 +2972,9 @@ async fn put_group_members(
|
||||
data: Json<Vec<MembershipId>>,
|
||||
mut conn: DbConn,
|
||||
) -> EmptyResult {
|
||||
if org_id != headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
if !CONFIG.org_groups_enabled() {
|
||||
err!("Group support is disabled");
|
||||
}
|
||||
@@ -3067,7 +3159,7 @@ async fn get_organization_public_key(
|
||||
headers: OrgMemberHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
if org_id != headers.org_id {
|
||||
if org_id != headers.membership.org_uuid {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
let Some(org) = Organization::find_by_uuid(&org_id, &mut conn).await else {
|
||||
@@ -3081,7 +3173,7 @@ async fn get_organization_public_key(
|
||||
}
|
||||
|
||||
// Obsolete - Renamed to public-key (2023.8), left for backwards compatibility with older clients
|
||||
// https://github.com/bitwarden/server/blob/25dc0c9178e3e3584074bbef0d4be827b7c89415/src/Api/AdminConsole/Controllers/OrganizationsController.cs#L463-L468
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Controllers/OrganizationsController.cs#L487-L492
|
||||
#[get("/organizations/<org_id>/keys")]
|
||||
async fn get_organization_keys(org_id: OrganizationId, headers: OrgMemberHeaders, conn: DbConn) -> JsonResult {
|
||||
get_organization_public_key(org_id, headers, conn).await
|
||||
@@ -3132,7 +3224,7 @@ async fn put_reset_password(
|
||||
user.set_password(reset_request.new_master_password_hash.as_str(), Some(reset_request.key), true, None);
|
||||
user.save(&mut conn).await?;
|
||||
|
||||
nt.send_logout(&user, None).await;
|
||||
nt.send_logout(&user, None, &mut conn).await;
|
||||
|
||||
log_event(
|
||||
EventType::OrganizationUserAdminResetPassword as i32,
|
||||
@@ -3172,16 +3264,16 @@ async fn get_reset_password_details(
|
||||
|
||||
check_reset_password_applicable_and_permissions(&org_id, &member_id, &headers, &mut conn).await?;
|
||||
|
||||
// https://github.com/bitwarden/server/blob/3b50ccb9f804efaacdc46bed5b60e5b28eddefcf/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs#L111
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Models/Response/Organizations/OrganizationUserResponseModel.cs#L190
|
||||
Ok(Json(json!({
|
||||
"object": "organizationUserResetPasswordDetails",
|
||||
"kdf":user.client_kdf_type,
|
||||
"kdfIterations":user.client_kdf_iter,
|
||||
"kdfMemory":user.client_kdf_memory,
|
||||
"kdfParallelism":user.client_kdf_parallelism,
|
||||
"resetPasswordKey":member.reset_password_key,
|
||||
"encryptedPrivateKey":org.private_key,
|
||||
|
||||
"organizationUserId": member_id,
|
||||
"kdf": user.client_kdf_type,
|
||||
"kdfIterations": user.client_kdf_iter,
|
||||
"kdfMemory": user.client_kdf_memory,
|
||||
"kdfParallelism": user.client_kdf_parallelism,
|
||||
"resetPasswordKey": member.reset_password_key,
|
||||
"encryptedPrivateKey": org.private_key,
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -3269,6 +3361,9 @@ async fn put_reset_password_enrollment(
|
||||
// NOTE: It seems clients can't handle uppercase-first keys!!
|
||||
// We need to convert all keys so they have the first character to be a lowercase.
|
||||
// Else the export will be just an empty JSON file.
|
||||
// We currently only support exports by members of the Admin or Owner status.
|
||||
// Vaultwarden does not yet support exporting only managed collections!
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Tools/Controllers/OrganizationExportController.cs#L52
|
||||
#[get("/organizations/<org_id>/export")]
|
||||
async fn get_org_export(org_id: OrganizationId, headers: AdminHeaders, mut conn: DbConn) -> JsonResult {
|
||||
if org_id != headers.org_id {
|
||||
@@ -3288,6 +3383,9 @@ async fn _api_key(
|
||||
headers: AdminHeaders,
|
||||
mut conn: DbConn,
|
||||
) -> JsonResult {
|
||||
if org_id != &headers.org_id {
|
||||
err!("Organization not found", "Organization id's do not match");
|
||||
}
|
||||
let data: PasswordOrOtpData = data.into_inner();
|
||||
let user = headers.user;
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ struct OrgImportData {
|
||||
#[post("/public/organization/import", data = "<data>")]
|
||||
async fn ldap_import(data: Json<OrgImportData>, token: PublicToken, mut conn: DbConn) -> EmptyResult {
|
||||
// Most of the logic for this function can be found here
|
||||
// https://github.com/bitwarden/server/blob/fd892b2ff4547648a276734fb2b14a8abae2c6f5/src/Core/Services/Implementations/OrganizationService.cs#L1797
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs#L1203
|
||||
|
||||
let org_id = token.0;
|
||||
let data = data.into_inner();
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::path::Path;
|
||||
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use num_traits::ToPrimitive;
|
||||
use once_cell::sync::Lazy;
|
||||
use rocket::form::Form;
|
||||
use rocket::fs::NamedFile;
|
||||
use rocket::fs::TempFile;
|
||||
@@ -17,6 +18,21 @@ use crate::{
|
||||
};
|
||||
|
||||
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available";
|
||||
static ANON_PUSH_DEVICE: Lazy<Device> = Lazy::new(|| {
|
||||
let dt = crate::util::parse_date("1970-01-01T00:00:00.000000Z");
|
||||
Device {
|
||||
uuid: String::from("00000000-0000-0000-0000-000000000000").into(),
|
||||
created_at: dt,
|
||||
updated_at: dt,
|
||||
user_uuid: String::from("00000000-0000-0000-0000-000000000000").into(),
|
||||
name: String::new(),
|
||||
atype: 14, // 14 == Unknown Browser
|
||||
push_uuid: Some(String::from("00000000-0000-0000-0000-000000000000").into()),
|
||||
push_token: None,
|
||||
refresh_token: String::new(),
|
||||
twofactor_remember: None,
|
||||
}
|
||||
});
|
||||
|
||||
// The max file size allowed by Bitwarden clients and add an extra 5% to avoid issues
|
||||
const SIZE_525_MB: i64 = 550_502_400;
|
||||
@@ -182,7 +198,7 @@ async fn post_send(data: Json<SendData>, headers: Headers, mut conn: DbConn, nt:
|
||||
UpdateType::SyncSendCreate,
|
||||
&send,
|
||||
&send.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
@@ -204,6 +220,8 @@ struct UploadDataV2<'f> {
|
||||
// @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads (v2).
|
||||
// This method still exists to support older clients, probably need to remove it sometime.
|
||||
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L164-L167
|
||||
// 2025: This endpoint doesn't seem to exists anymore in the latest version
|
||||
// See: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Tools/Controllers/SendsController.cs
|
||||
#[post("/sends/file", format = "multipart/form-data", data = "<data>")]
|
||||
async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||
enforce_disable_send_policy(&headers, &mut conn).await?;
|
||||
@@ -272,7 +290,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
|
||||
UpdateType::SyncSendCreate,
|
||||
&send,
|
||||
&send.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
@@ -280,7 +298,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
|
||||
Ok(Json(send.to_json()))
|
||||
}
|
||||
|
||||
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L190
|
||||
// Upstream: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Tools/Controllers/SendsController.cs#L165
|
||||
#[post("/sends/file/v2", data = "<data>")]
|
||||
async fn post_send_file_v2(data: Json<SendData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
enforce_disable_send_policy(&headers, &mut conn).await?;
|
||||
@@ -351,7 +369,7 @@ pub struct SendFileData {
|
||||
fileName: String,
|
||||
}
|
||||
|
||||
// https://github.com/bitwarden/server/blob/66f95d1c443490b653e5a15d32977e2f5a3f9e32/src/Api/Tools/Controllers/SendsController.cs#L250
|
||||
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Tools/Controllers/SendsController.cs#L195
|
||||
#[post("/sends/<send_id>/file/<file_id>", format = "multipart/form-data", data = "<data>")]
|
||||
async fn post_send_file_v2_data(
|
||||
send_id: SendId,
|
||||
@@ -424,7 +442,7 @@ async fn post_send_file_v2_data(
|
||||
UpdateType::SyncSendCreate,
|
||||
&send,
|
||||
&send.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
@@ -489,7 +507,7 @@ async fn post_access(
|
||||
UpdateType::SyncSendUpdate,
|
||||
&send,
|
||||
&send.update_users_revision(&mut conn).await,
|
||||
&String::from("00000000-0000-0000-0000-000000000000").into(),
|
||||
&ANON_PUSH_DEVICE,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
@@ -546,7 +564,7 @@ async fn post_access_file(
|
||||
UpdateType::SyncSendUpdate,
|
||||
&send,
|
||||
&send.update_users_revision(&mut conn).await,
|
||||
&String::from("00000000-0000-0000-0000-000000000000").into(),
|
||||
&ANON_PUSH_DEVICE,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
@@ -645,7 +663,7 @@ pub async fn update_send_from_data(
|
||||
|
||||
send.save(conn).await?;
|
||||
if ut != UpdateType::None {
|
||||
nt.send_send_update(ut, send, &send.update_users_revision(conn).await, &headers.device.uuid, conn).await;
|
||||
nt.send_send_update(ut, send, &send.update_users_revision(conn).await, &headers.device, conn).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -661,7 +679,7 @@ async fn delete_send(send_id: SendId, headers: Headers, mut conn: DbConn, nt: No
|
||||
UpdateType::SyncSendDelete,
|
||||
&send,
|
||||
&send.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
@@ -683,7 +701,7 @@ async fn put_remove_password(send_id: SendId, headers: Headers, mut conn: DbConn
|
||||
UpdateType::SyncSendUpdate,
|
||||
&send,
|
||||
&send.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
&headers.device,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -34,6 +34,10 @@ async fn generate_authenticator(data: Json<PasswordOrOtpData>, headers: Headers,
|
||||
_ => (false, crypto::encode_random_bytes::<20>(BASE32)),
|
||||
};
|
||||
|
||||
// Upstream seems to also return `userVerificationToken`, but doesn't seem to be used at all.
|
||||
// It should help prevent TOTP disclosure if someone keeps their vault unlocked.
|
||||
// Since it doesn't seem to be used, and also does not cause any issues, lets leave it out of the response.
|
||||
// See: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Auth/Controllers/TwoFactorController.cs#L94
|
||||
Ok(Json(json!({
|
||||
"enabled": enabled,
|
||||
"key": key,
|
||||
|
||||
@@ -118,6 +118,9 @@ async fn get_duo(data: Json<PasswordOrOtpData>, headers: Headers, mut conn: DbCo
|
||||
} else {
|
||||
json!({
|
||||
"enabled": enabled,
|
||||
"host": null,
|
||||
"clientSecret": null,
|
||||
"clientId": null,
|
||||
"object": "twoFactorDuo"
|
||||
})
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ use url::Url;
|
||||
|
||||
// The location on this service that Duo should redirect users to. For us, this is a bridge
|
||||
// built in to the Bitwarden clients.
|
||||
// See: https://github.com/bitwarden/clients/blob/main/apps/web/src/connectors/duo-redirect.ts
|
||||
// See: https://github.com/bitwarden/clients/blob/5fb46df3415aefced0b52f2db86c873962255448/apps/web/src/connectors/duo-redirect.ts
|
||||
const DUO_REDIRECT_LOCATION: &str = "duo-redirect-connector.html";
|
||||
|
||||
// Number of seconds that a JWT we generate for Duo should be valid for.
|
||||
|
||||
Reference in New Issue
Block a user