Browse Source

Merge 283467f90e into d6a3d539ed

pull/7320/merge
svrforum 5 days ago
committed by GitHub
parent
commit
6d9b9b069a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      src/api/core/events.rs
  2. 52
      src/api/core/public.rs
  3. 52
      src/db/models/event.rs

2
src/api/core/events.rs

@ -125,7 +125,7 @@ async fn get_user_events(
})))
}
fn get_continuation_token(events_json: &[Value]) -> Option<&str> {
pub(crate) fn get_continuation_token(events_json: &[Value]) -> Option<&str> {
// When the length of the vec equals the max page_size there probably is more data
// When it is less, then all events are loaded.
#[expect(clippy::cast_possible_truncation, reason = "PAGE_SIZE fits within usize")]

52
src/api/core/public.rs

@ -1,28 +1,72 @@
use std::collections::HashSet;
use chrono::Utc;
use chrono::{TimeDelta, Utc};
use rocket::{
Request, Route,
form::FromForm,
request::{FromRequest, Outcome},
serde::json::Json,
};
use serde_json::Value;
use crate::{
CONFIG,
api::EmptyResult,
api::{EmptyResult, JsonResult, core::events::get_continuation_token},
auth,
db::{
DbConn,
models::{
Group, GroupUser, Invitation, Membership, MembershipStatus, MembershipType, Organization,
Event, Group, GroupUser, Invitation, Membership, MembershipStatus, MembershipType, Organization,
OrganizationApiKey, OrganizationId, User,
},
},
mail,
util::parse_date,
};
pub fn routes() -> Vec<Route> {
routes![ldap_import]
routes![ldap_import, get_org_events]
}
#[derive(FromForm)]
struct EventRangePublic {
start: Option<String>,
end: Option<String>,
#[field(name = "continuationToken")]
continuation_token: Option<String>,
}
// Upstream: https://github.com/bitwarden/server/blob/main/src/Api/AdminConsole/Public/Controllers/EventsController.cs
#[get("/public/events?<data..>")]
async fn get_org_events(data: EventRangePublic, token: PublicToken, conn: DbConn) -> JsonResult {
let org_id = token.0;
// Return an empty list when org events are disabled, to mirror the internal endpoints.
let events_json: Vec<Value> = if CONFIG.org_events_enabled() {
let now = Utc::now().naive_utc();
// Defaults match upstream ApiHelpers.GetDateRange: end = now, start = end - 1 day.
let start_date = data.start.as_deref().map_or_else(|| now - TimeDelta::try_days(1).unwrap(), parse_date);
// The continuation token, when present, becomes the new `end` cursor (same as the internal endpoints).
let end_date = match (&data.continuation_token, &data.end) {
(Some(token), _) => parse_date(token),
(None, Some(end)) => parse_date(end),
(None, None) => now,
};
Event::find_by_organization_uuid(&org_id, &start_date, &end_date, &conn)
.await
.iter()
.map(Event::to_json_public)
.collect()
} else {
Vec::with_capacity(0)
};
Ok(Json(json!({
"data": events_json,
"object": "list",
"continuationToken": get_continuation_token(&events_json),
})))
}
#[derive(Deserialize)]

52
src/db/models/event.rs

@ -191,6 +191,26 @@ impl Event {
// "installationId": null, // Not supported
})
}
// Upstream: https://github.com/bitwarden/server/blob/main/src/Api/AdminConsole/Public/Models/Response/EventResponseModel.cs
pub fn to_json_public(&self) -> Value {
use crate::util::format_date;
json!({
"object": "event",
"type": self.event_type,
"itemId": self.cipher_uuid,
"collectionId": self.collection_uuid,
"groupId": self.group_uuid,
"policyId": self.policy_uuid,
"memberId": self.org_user_uuid,
"actingUserId": self.act_user_uuid,
"installationId": null, // Not supported
"date": format_date(&self.event_date),
"deviceType": self.device_type,
"ipAddress": self.ip_address,
})
}
}
/// Database methods
@ -350,3 +370,35 @@ impl Event {
#[derive(Clone, Debug, DieselNewType, FromForm, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct EventId(String);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_json_public_uses_public_api_field_names() {
// OrganizationUserUpdated event with a member + cipher reference
let mut event = Event::new(EventType::OrganizationUserUpdated as i32, None);
event.org_uuid = Some("11111111-1111-1111-1111-111111111111".to_string().into());
event.org_user_uuid = Some("22222222-2222-2222-2222-222222222222".to_string().into());
event.cipher_uuid = Some("33333333-3333-3333-3333-333333333333".to_string().into());
event.act_user_uuid = Some("44444444-4444-4444-4444-444444444444".to_string().into());
let json = event.to_json_public();
// Public API field names
assert_eq!(json["object"], "event");
assert_eq!(json["type"], EventType::OrganizationUserUpdated as i32);
assert_eq!(json["itemId"], "33333333-3333-3333-3333-333333333333");
assert_eq!(json["memberId"], "22222222-2222-2222-2222-222222222222");
assert_eq!(json["actingUserId"], "44444444-4444-4444-4444-444444444444");
assert!(json["installationId"].is_null());
// Internal-only keys MUST NOT be present in the public model
assert!(json.get("cipherId").is_none());
assert!(json.get("organizationUserId").is_none());
assert!(json.get("organizationId").is_none());
assert!(json.get("userId").is_none());
assert!(json.get("providerId").is_none());
}
}

Loading…
Cancel
Save