mirror of
https://github.com/dani-garcia/vaultwarden
synced 2024-11-11 20:42:50 +01:00
Fix organization vault export
Since v2022.9.x it seems they changed the export endpoint and way of working. This PR fixes this by adding the export endpoint. Also, it looks like the clients can't handle uppercase first JSON key's. Because of this there now is a function which converts all the key's to lowercase first. I have an issue reported at Bitwarden if this is expected behavior: https://github.com/bitwarden/clients/issues/3606 Fixes #2760 Fixes #2764
This commit is contained in:
parent
5b8067ef77
commit
da9fd6b7d0
2 changed files with 90 additions and 10 deletions
|
@ -10,7 +10,9 @@ use crate::{
|
||||||
},
|
},
|
||||||
auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
|
auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
|
||||||
db::{models::*, DbConn},
|
db::{models::*, DbConn},
|
||||||
mail, CONFIG,
|
mail,
|
||||||
|
util::convert_json_key_lcase_first,
|
||||||
|
CONFIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures::{stream, stream::StreamExt};
|
use futures::{stream, stream::StreamExt};
|
||||||
|
@ -68,7 +70,8 @@ pub fn routes() -> Vec<Route> {
|
||||||
activate_organization_user,
|
activate_organization_user,
|
||||||
bulk_activate_organization_user,
|
bulk_activate_organization_user,
|
||||||
restore_organization_user,
|
restore_organization_user,
|
||||||
bulk_restore_organization_user
|
bulk_restore_organization_user,
|
||||||
|
get_org_export
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,15 +249,19 @@ async fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> {
|
||||||
|
|
||||||
#[get("/organizations/<org_id>/collections")]
|
#[get("/organizations/<org_id>/collections")]
|
||||||
async fn get_org_collections(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> Json<Value> {
|
async fn get_org_collections(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> Json<Value> {
|
||||||
Json(json!({
|
Json(_get_org_collections(&org_id, &conn).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _get_org_collections(org_id: &str, conn: &DbConn) -> Value {
|
||||||
|
json!({
|
||||||
"Data":
|
"Data":
|
||||||
Collection::find_by_organization(&org_id, &conn).await
|
Collection::find_by_organization(org_id, conn).await
|
||||||
.iter()
|
.iter()
|
||||||
.map(Collection::to_json)
|
.map(Collection::to_json)
|
||||||
.collect::<Value>(),
|
.collect::<Value>(),
|
||||||
"Object": "list",
|
"Object": "list",
|
||||||
"ContinuationToken": null,
|
"ContinuationToken": null,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/organizations/<org_id>/collections", data = "<data>")]
|
#[post("/organizations/<org_id>/collections", data = "<data>")]
|
||||||
|
@ -491,22 +498,26 @@ struct OrgIdData {
|
||||||
|
|
||||||
#[get("/ciphers/organization-details?<data..>")]
|
#[get("/ciphers/organization-details?<data..>")]
|
||||||
async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> {
|
async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> {
|
||||||
let ciphers = Cipher::find_by_org(&data.organization_id, &conn).await;
|
Json(_get_org_details(&data.organization_id, &headers.host, &headers.user.uuid, &conn).await)
|
||||||
let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::Organization, &conn).await;
|
}
|
||||||
|
|
||||||
|
async fn _get_org_details(org_id: &str, host: &str, user_uuid: &str, conn: &DbConn) -> Value {
|
||||||
|
let ciphers = Cipher::find_by_org(org_id, conn).await;
|
||||||
|
let cipher_sync_data = CipherSyncData::new(user_uuid, &ciphers, CipherSyncType::Organization, conn).await;
|
||||||
|
|
||||||
let ciphers_json = stream::iter(ciphers)
|
let ciphers_json = stream::iter(ciphers)
|
||||||
.then(|c| async {
|
.then(|c| async {
|
||||||
let c = c; // Move out this single variable
|
let c = c; // Move out this single variable
|
||||||
c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &conn).await
|
c.to_json(host, user_uuid, Some(&cipher_sync_data), conn).await
|
||||||
})
|
})
|
||||||
.collect::<Vec<Value>>()
|
.collect::<Vec<Value>>()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Json(json!({
|
json!({
|
||||||
"Data": ciphers_json,
|
"Data": ciphers_json,
|
||||||
"Object": "list",
|
"Object": "list",
|
||||||
"ContinuationToken": null,
|
"ContinuationToken": null,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/organizations/<org_id>/users")]
|
#[get("/organizations/<org_id>/users")]
|
||||||
|
@ -1690,3 +1701,19 @@ async fn _restore_organization_user(
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is a new function active since the v2022.9.x clients.
|
||||||
|
// It combines the previous two calls done before.
|
||||||
|
// We call those two functions here and combine them our selfs.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
#[get("/organizations/<org_id>/export")]
|
||||||
|
async fn get_org_export(org_id: String, headers: AdminHeaders, conn: DbConn) -> Json<Value> {
|
||||||
|
// Also both main keys here need to be lowercase, else the export will fail.
|
||||||
|
Json(json!({
|
||||||
|
"collections": convert_json_key_lcase_first(_get_org_collections(&org_id, &conn).await),
|
||||||
|
"ciphers": convert_json_key_lcase_first(_get_org_details(&org_id, &headers.host, &headers.user.uuid, &conn).await),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
53
src/util.rs
53
src/util.rs
|
@ -357,6 +357,7 @@ pub fn get_uuid() -> String {
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn upcase_first(s: &str) -> String {
|
pub fn upcase_first(s: &str) -> String {
|
||||||
let mut c = s.chars();
|
let mut c = s.chars();
|
||||||
match c.next() {
|
match c.next() {
|
||||||
|
@ -365,6 +366,15 @@ pub fn upcase_first(s: &str) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn lcase_first(s: &str) -> String {
|
||||||
|
let mut c = s.chars();
|
||||||
|
match c.next() {
|
||||||
|
None => String::new(),
|
||||||
|
Some(f) => f.to_lowercase().collect::<String>() + c.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_parse_string<S, T>(string: Option<S>) -> Option<T>
|
pub fn try_parse_string<S, T>(string: Option<S>) -> Option<T>
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
@ -650,3 +660,46 @@ pub fn get_reqwest_client_builder() -> ClientBuilder {
|
||||||
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden"));
|
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Vaultwarden"));
|
||||||
Client::builder().default_headers(headers).timeout(Duration::from_secs(10))
|
Client::builder().default_headers(headers).timeout(Duration::from_secs(10))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn convert_json_key_lcase_first(src_json: Value) -> Value {
|
||||||
|
match src_json {
|
||||||
|
Value::Array(elm) => {
|
||||||
|
let mut new_array: Vec<Value> = Vec::with_capacity(elm.len());
|
||||||
|
|
||||||
|
for obj in elm {
|
||||||
|
new_array.push(convert_json_key_lcase_first(obj));
|
||||||
|
}
|
||||||
|
Value::Array(new_array)
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Object(obj) => {
|
||||||
|
let mut json_map = JsonMap::new();
|
||||||
|
for (key, value) in obj.iter() {
|
||||||
|
match (key, value) {
|
||||||
|
(key, Value::Object(elm)) => {
|
||||||
|
let inner_value = convert_json_key_lcase_first(Value::Object(elm.clone()));
|
||||||
|
json_map.insert(lcase_first(key), inner_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
(key, Value::Array(elm)) => {
|
||||||
|
let mut inner_array: Vec<Value> = Vec::with_capacity(elm.len());
|
||||||
|
|
||||||
|
for inner_obj in elm {
|
||||||
|
inner_array.push(convert_json_key_lcase_first(inner_obj.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_map.insert(lcase_first(key), Value::Array(inner_array));
|
||||||
|
}
|
||||||
|
|
||||||
|
(key, value) => {
|
||||||
|
json_map.insert(lcase_first(key), value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Object(json_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
value => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue