mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-06-09 12:33:53 +00:00
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>
This commit is contained in:
parent
3a44dc963b
commit
bdb46fee2d
11 changed files with 104 additions and 40 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -409,9 +409,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "8.0.1"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d"
|
||||
checksum = "cf19e729cdbd51af9a397fb9ef8ac8378007b797f8273cfbfdf45dcaa316167b"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
|
@ -490,9 +490,9 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.23"
|
||||
version = "1.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
|
||||
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
@ -1367,9 +1367,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.10"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
|
||||
checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
|
@ -1648,7 +1648,7 @@ dependencies = [
|
|||
"http 1.3.1",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.27",
|
||||
"rustls 0.23.26",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.2",
|
||||
|
@ -1985,9 +1985,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
|||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72"
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
|
@ -3221,9 +3221,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.27"
|
||||
version = "0.23.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
|
||||
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustls-pki-types",
|
||||
|
@ -3851,7 +3851,7 @@ version = "0.26.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
|
||||
dependencies = [
|
||||
"rustls 0.23.27",
|
||||
"rustls 0.23.26",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
@ -3934,8 +3934,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow 0.7.10",
|
||||
"winnow 0.7.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4812,9 +4811,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.10"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
|
||||
checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -176,6 +176,7 @@ rpassword = "7.4.0"
|
|||
# Loading a dynamic CSS Stylesheet
|
||||
grass_compiler = { version = "0.13.4", default-features = false }
|
||||
|
||||
|
||||
# Strip debuginfo from the release builds
|
||||
# The debug symbols are to provide better panic traces
|
||||
# Also enable fat LTO and use 1 codegen unit for optimizations
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -203,6 +203,7 @@ fn config() -> Json<Value> {
|
|||
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("duo-redirect".to_string(), true);
|
||||
feature_states.insert("flexible-collections-v-1".to_string(), false);
|
||||
|
||||
feature_states.insert("email-verification".to_string(), true);
|
||||
|
@ -216,6 +217,7 @@ fn config() -> Json<Value> {
|
|||
// - Individual cipher key encryption: 2024.2.0
|
||||
"version": "2025.1.0",
|
||||
"gitHash": option_env!("GIT_REV"),
|
||||
"cloudRegion": null,
|
||||
"server": {
|
||||
"name": "Vaultwarden",
|
||||
"url": "https://github.com/dani-garcia/vaultwarden"
|
||||
|
@ -230,6 +232,11 @@ fn config() -> Json<Value> {
|
|||
"notifications": format!("{domain}/notifications"),
|
||||
"sso": "",
|
||||
},
|
||||
// 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, ownners 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() {
|
||||
|
@ -2556,18 +2572,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 +2608,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)]
|
||||
|
@ -2740,7 +2770,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"
|
||||
})))
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
|
|||
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
|
||||
// ---
|
||||
// let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
|
||||
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
|
||||
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec, data.client_id);
|
||||
device.save(conn).await?;
|
||||
|
||||
let result = json!({
|
||||
|
@ -297,7 +297,7 @@ async fn _password_login(
|
|||
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
|
||||
// ---
|
||||
// let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
|
||||
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
|
||||
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec, data.client_id);
|
||||
device.save(conn).await?;
|
||||
|
||||
// Fetch all valid Master Password Policies and merge them into one with all true's and larges numbers as one policy
|
||||
|
@ -312,6 +312,7 @@ async fn _password_login(
|
|||
.filter_map(|p| serde_json::from_str(&p.data).ok())
|
||||
.collect();
|
||||
|
||||
// NOTE: Upstream still uses PascalCase here for `Object`!
|
||||
let master_password_policy = if !master_password_policies.is_empty() {
|
||||
let mut mpp_json = json!(master_password_policies.into_iter().reduce(|acc, policy| {
|
||||
MasterPasswordPolicy {
|
||||
|
@ -324,10 +325,10 @@ async fn _password_login(
|
|||
enforce_on_login: acc.enforce_on_login || policy.enforce_on_login,
|
||||
}
|
||||
}));
|
||||
mpp_json["object"] = json!("masterPasswordPolicy");
|
||||
mpp_json["Object"] = json!("masterPasswordPolicy");
|
||||
mpp_json
|
||||
} else {
|
||||
json!({"object": "masterPasswordPolicy"})
|
||||
json!({"Object": "masterPasswordPolicy"})
|
||||
};
|
||||
|
||||
let mut result = json!({
|
||||
|
@ -447,7 +448,7 @@ async fn _user_api_key_login(
|
|||
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
|
||||
// ---
|
||||
// let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
|
||||
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
|
||||
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec, data.client_id);
|
||||
device.save(conn).await?;
|
||||
|
||||
info!("User {} logged in successfully via API key. IP: {}", user.email, ip.ip);
|
||||
|
|
|
@ -181,6 +181,11 @@ pub struct LoginJwtClaims {
|
|||
pub sstamp: String,
|
||||
// device uuid
|
||||
pub device: DeviceId,
|
||||
// what kind of device, like FirefoxBrowser or Android derived from DeviceType
|
||||
pub devicetype: String,
|
||||
// the type of client_id, like web, cli, desktop, browser or mobile
|
||||
pub client_id: String,
|
||||
|
||||
// [ "api", "offline_access" ]
|
||||
pub scope: Vec<String>,
|
||||
// [ "Application" ]
|
||||
|
|
|
@ -54,7 +54,7 @@ impl Device {
|
|||
"id": self.uuid,
|
||||
"name": self.name,
|
||||
"type": self.atype,
|
||||
"identifier": self.push_uuid,
|
||||
"identifier": self.uuid,
|
||||
"creationDate": format_date(&self.created_at),
|
||||
"isTrusted": false,
|
||||
"object":"device"
|
||||
|
@ -73,7 +73,12 @@ impl Device {
|
|||
self.twofactor_remember = None;
|
||||
}
|
||||
|
||||
pub fn refresh_tokens(&mut self, user: &super::User, scope: Vec<String>) -> (String, i64) {
|
||||
pub fn refresh_tokens(
|
||||
&mut self,
|
||||
user: &super::User,
|
||||
scope: Vec<String>,
|
||||
client_id: Option<String>,
|
||||
) -> (String, i64) {
|
||||
// If there is no refresh token, we create one
|
||||
if self.refresh_token.is_empty() {
|
||||
use data_encoding::BASE64URL;
|
||||
|
@ -121,6 +126,8 @@ impl Device {
|
|||
// orgmanager,
|
||||
sstamp: user.security_stamp.clone(),
|
||||
device: self.uuid.clone(),
|
||||
devicetype: DeviceType::from_i32(self.atype).to_string(),
|
||||
client_id: client_id.unwrap_or("undefined".to_string()),
|
||||
scope,
|
||||
amr: vec!["Application".into()],
|
||||
};
|
||||
|
@ -156,7 +163,7 @@ impl DeviceWithAuthRequest {
|
|||
"id": self.device.uuid,
|
||||
"name": self.device.name,
|
||||
"type": self.device.atype,
|
||||
"identifier": self.device.push_uuid,
|
||||
"identifier": self.device.uuid,
|
||||
"creationDate": format_date(&self.device.created_at),
|
||||
"devicePendingAuthRequest": auth_request,
|
||||
"isTrusted": false,
|
||||
|
|
|
@ -68,16 +68,11 @@ impl Group {
|
|||
}
|
||||
|
||||
pub fn to_json(&self) -> Value {
|
||||
use crate::util::format_date;
|
||||
|
||||
json!({
|
||||
"id": self.uuid,
|
||||
"organizationId": self.organizations_uuid,
|
||||
"name": self.name,
|
||||
"accessAll": self.access_all,
|
||||
"externalId": self.external_id,
|
||||
"creationDate": format_date(&self.creation_date),
|
||||
"revisionDate": format_date(&self.revision_date),
|
||||
"object": "group"
|
||||
})
|
||||
}
|
||||
|
|
|
@ -451,6 +451,8 @@ impl Membership {
|
|||
"usePasswordManager": true,
|
||||
"useCustomPermissions": true,
|
||||
"useActivateAutofillPolicy": false,
|
||||
"useAdminSponsoredFamilies": false,
|
||||
"useRiskInsights": false, // Not supported (Not AGPLv3 Licensed)
|
||||
|
||||
"organizationUserId": self.uuid,
|
||||
"providerId": null,
|
||||
|
@ -458,7 +460,6 @@ impl Membership {
|
|||
"providerType": null,
|
||||
"familySponsorshipFriendlyName": null,
|
||||
"familySponsorshipAvailable": false,
|
||||
"planProductType": 3,
|
||||
"productTierType": 3, // Enterprise tier
|
||||
"keyConnectorEnabled": false,
|
||||
"keyConnectorUrl": null,
|
||||
|
@ -469,8 +470,10 @@ impl Membership {
|
|||
"limitCollectionCreation": self.atype < MembershipType::Manager, // If less then a manager return true, to limit collection creations
|
||||
"limitCollectionCreationDeletion": true,
|
||||
"limitCollectionDeletion": true,
|
||||
"limitItemDeletion": false,
|
||||
"allowAdminAccessToAllCollectionItems": true,
|
||||
"userIsManagedByOrganization": false, // Means not managed via the Members UI, like SSO
|
||||
"userIsClaimedByOrganization": false, // The new key instead of the obsolete userIsManagedByOrganization
|
||||
|
||||
"permissions": permissions,
|
||||
|
||||
|
@ -616,6 +619,8 @@ impl Membership {
|
|||
"permissions": permissions,
|
||||
|
||||
"ssoBound": false, // Not supported
|
||||
"managedByOrganization": false, // This key is obsolete replaced by claimedByOrganization
|
||||
"claimedByOrganization": false, // Means not managed via the Members UI, like SSO
|
||||
"usesKeyConnector": false, // Not supported
|
||||
"accessSecretsManager": false, // Not supported (Not AGPLv3 Licensed)
|
||||
|
||||
|
@ -863,6 +868,21 @@ impl Membership {
|
|||
}}
|
||||
}
|
||||
|
||||
// Get all users which are either owner or admin, or a manager which can manage/access all
|
||||
pub async fn find_confirmed_and_manage_all_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
.filter(users_organizations::org_uuid.eq(org_uuid))
|
||||
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
|
||||
.filter(
|
||||
users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32])
|
||||
.or(users_organizations::atype.eq(MembershipType::Manager as i32).and(users_organizations::access_all.eq(true)))
|
||||
)
|
||||
.load::<MembershipDb>(conn)
|
||||
.unwrap_or_default().from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn count_by_org(org_uuid: &OrganizationId, conn: &mut DbConn) -> i64 {
|
||||
db_run! { conn: {
|
||||
users_organizations::table
|
||||
|
|
|
@ -249,7 +249,6 @@ impl User {
|
|||
"emailVerified": !CONFIG.mail_enabled() || self.verified_at.is_some(),
|
||||
"premium": true,
|
||||
"premiumFromOrganization": false,
|
||||
"masterPasswordHint": self.password_hint,
|
||||
"culture": "en-US",
|
||||
"twoFactorEnabled": twofactor_enabled,
|
||||
"key": self.akey,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue