Browse Source

Implement accessReports custom permission

pull/7397/head
tom27052006 3 weeks ago
parent
commit
3c5b846d03
  1. 20
      src/api/core/organizations.rs
  2. 9
      src/auth.rs
  3. 10
      src/db/models/organization.rs

20
src/api/core/organizations.rs

@ -1237,9 +1237,7 @@ impl CustomRolePermissions {
delete_any_collection: enabled("deleteAnyCollection"), delete_any_collection: enabled("deleteAnyCollection"),
access_event_logs: enabled("accessEventLogs"), access_event_logs: enabled("accessEventLogs"),
access_import_export: enabled("accessImportExport"), access_import_export: enabled("accessImportExport"),
// Vaultwarden has no report endpoints yet. Keep the compatibility field in the access_reports: enabled("accessReports"),
// database/DTO, but never accept a permission that cannot be enforced server-side.
access_reports: false,
} }
} }
@ -1271,8 +1269,7 @@ impl CustomRolePermissions {
delete_any_collection: membership.delete_any_collection, delete_any_collection: membership.delete_any_collection,
access_event_logs: membership.access_event_logs, access_event_logs: membership.access_event_logs,
access_import_export: membership.access_import_export, access_import_export: membership.access_import_export,
// Reports are unsupported and therefore never preserved as an active grant. access_reports: membership.access_reports,
access_reports: false,
} }
} }
None => Self::default(), None => Self::default(),
@ -1288,6 +1285,7 @@ impl CustomRolePermissions {
|| self.delete_any_collection != membership.delete_any_collection || self.delete_any_collection != membership.delete_any_collection
|| self.access_event_logs != membership.access_event_logs || self.access_event_logs != membership.access_event_logs
|| self.access_import_export != membership.access_import_export || self.access_import_export != membership.access_import_export
|| self.access_reports != membership.access_reports
} }
fn apply_to(self, membership: &mut Membership) { fn apply_to(self, membership: &mut Membership) {
@ -4214,7 +4212,7 @@ mod tests {
assert!(custom.delete_any_collection); assert!(custom.delete_any_collection);
assert!(custom.access_event_logs); assert!(custom.access_event_logs);
assert!(custom.access_import_export); assert!(custom.access_import_export);
assert!(!custom.access_reports, "unsupported report access must remain fail-closed"); assert!(custom.access_reports);
let user = CustomRolePermissions::from_request(MembershipType::User, &permissions); let user = CustomRolePermissions::from_request(MembershipType::User, &permissions);
assert_eq!(user, CustomRolePermissions::default()); assert_eq!(user, CustomRolePermissions::default());
@ -4237,6 +4235,7 @@ mod tests {
delete_any_collection: true, delete_any_collection: true,
access_event_logs: true, access_event_logs: true,
access_import_export: true, access_import_export: true,
access_reports: true,
..CustomRolePermissions::default() ..CustomRolePermissions::default()
}; };
@ -4248,7 +4247,7 @@ mod tests {
assert!(membership.delete_any_collection); assert!(membership.delete_any_collection);
assert!(membership.access_event_logs); assert!(membership.access_event_logs);
assert!(membership.access_import_export); assert!(membership.access_import_export);
assert!(!membership.access_reports); assert!(membership.access_reports);
} }
#[test] #[test]
@ -4273,11 +4272,8 @@ mod tests {
assert!(preserved.delete_any_collection); assert!(preserved.delete_any_collection);
assert!(preserved.access_event_logs); assert!(preserved.access_event_logs);
assert!(preserved.access_import_export); assert!(preserved.access_import_export);
assert!(!preserved.access_reports); assert!(preserved.access_reports);
assert!( assert!(!preserved.differs_from(&membership));
!preserved.differs_from(&membership),
"a stale unsupported reports bit must not block an otherwise unchanged legacy-client update"
);
let explicit_reset = HashMap::new(); let explicit_reset = HashMap::new();
assert_eq!( assert_eq!(

9
src/auth.rs

@ -766,6 +766,10 @@ impl OrgHeaders {
self.is_confirmed() self.is_confirmed()
&& (self.membership_type >= MembershipType::Admin || self.membership.has_access_import_export()) && (self.membership_type >= MembershipType::Admin || self.membership.has_access_import_export())
} }
fn can_access_reports(&self) -> bool {
self.is_confirmed() && (self.membership_type >= MembershipType::Admin || self.membership.has_access_reports())
}
} }
// org_id is usually the second path param ("/organizations/<org_id>"), // org_id is usually the second path param ("/organizations/<org_id>"),
@ -966,6 +970,11 @@ generate_manage_headers!(
can_access_import_export, can_access_import_export,
"You need the 'Access Import/Export' permission, or to be an Admin or Owner, to call this endpoint" "You need the 'Access Import/Export' permission, or to be an Admin or Owner, to call this endpoint"
); );
generate_manage_headers!(
AccessReportsHeaders,
can_access_reports,
"You need the 'Access Reports' permission, or to be an Admin or Owner, to call this endpoint"
);
// col_id is usually the fourth path param ("/organizations/<org_id>/collections/<col_id>"), // col_id is usually the fourth path param ("/organizations/<org_id>/collections/<col_id>"),
// but there could be cases where it is a query value. // but there could be cases where it is a query value.

10
src/db/models/organization.rs

@ -469,9 +469,7 @@ impl Membership {
let permissions = json!({ let permissions = json!({
"accessEventLogs": membership_type == MembershipType::Custom as i32 && self.access_event_logs, "accessEventLogs": membership_type == MembershipType::Custom as i32 && self.access_event_logs,
"accessImportExport": membership_type == MembershipType::Custom as i32 && self.access_import_export, "accessImportExport": membership_type == MembershipType::Custom as i32 && self.access_import_export,
// Reports are not implemented server-side. Advertising a stored bit as usable "accessReports": membership_type == MembershipType::Custom as i32 && self.access_reports,
// would make the permission contract misleading, so this stays fail-closed.
"accessReports": false,
"createNewCollections": membership_type == MembershipType::Custom as i32 && self.create_new_collections, "createNewCollections": membership_type == MembershipType::Custom as i32 && self.create_new_collections,
"editAnyCollection": membership_type == MembershipType::Custom as i32 && self.edit_any_collection, "editAnyCollection": membership_type == MembershipType::Custom as i32 && self.edit_any_collection,
"deleteAnyCollection": membership_type == MembershipType::Custom as i32 && self.delete_any_collection, "deleteAnyCollection": membership_type == MembershipType::Custom as i32 && self.delete_any_collection,
@ -639,7 +637,7 @@ impl Membership {
json!({ json!({
"accessEventLogs": self.access_event_logs, "accessEventLogs": self.access_event_logs,
"accessImportExport": self.access_import_export, "accessImportExport": self.access_import_export,
"accessReports": false, "accessReports": self.access_reports,
"createNewCollections": self.create_new_collections, "createNewCollections": self.create_new_collections,
"editAnyCollection": self.edit_any_collection, "editAnyCollection": self.edit_any_collection,
"deleteAnyCollection": self.delete_any_collection, "deleteAnyCollection": self.delete_any_collection,
@ -885,6 +883,10 @@ impl Membership {
self.has_type(MembershipType::Custom) && self.access_import_export self.has_type(MembershipType::Custom) && self.access_import_export
} }
pub fn has_access_reports(&self) -> bool {
self.has_type(MembershipType::Custom) && self.access_reports
}
/// Check for an explicit per-collection Manage grant without treating any `access_all` value /// Check for an explicit per-collection Manage grant without treating any `access_all` value
/// as such a grant. Custom-role collection guards use this instead of the legacy broad helper, /// as such a grant. Custom-role collection guards use this instead of the legacy broad helper,
/// because membership/group `access_all` must not manufacture a per-collection Manage grant. /// because membership/group `access_all` must not manufacture a per-collection Manage grant.

Loading…
Cancel
Save