Browse Source

Add Public API organization event log endpoint

Expose GET /public/events so an organization-scoped API client can read the
organization event log using its organization API key. The same data is
otherwise only reachable through the internal API, which requires an admin
user session.

Reuses the existing EventRange query model and continuation-token paging
helper from the internal events endpoints, so the request and response shapes
match /organizations/<org_id>/events.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pull/7570/head
Rune Darrud 5 days ago
parent
commit
26850247fa
  1. 10
      src/api/core/events.rs
  2. 44
      src/api/core/public.rs

10
src/api/core/events.rs

@ -22,11 +22,11 @@ pub fn routes() -> Vec<Route> {
}
#[derive(FromForm)]
struct EventRange {
start: String,
end: String,
pub struct EventRange {
pub start: String,
pub end: String,
#[field(name = "continuationToken")]
continuation_token: Option<String>,
pub continuation_token: Option<String>,
}
// Upstream: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Controllers/EventsController.cs#L87
@ -125,7 +125,7 @@ async fn get_user_events(
})))
}
fn get_continuation_token(events_json: &[Value]) -> Option<&str> {
pub 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")]

44
src/api/core/public.rs

@ -6,23 +6,27 @@ use rocket::{
request::{FromRequest, Outcome},
serde::json::Json,
};
use serde_json::Value;
use crate::{
CONFIG,
api::EmptyResult,
api::{EmptyResult, JsonResult},
auth,
db::{
DbConn,
models::{
Group, GroupUser, Invitation, Membership, MembershipStatus, MembershipType, OrgPolicy, Organization,
Event, Group, GroupUser, Invitation, Membership, MembershipStatus, MembershipType, OrgPolicy, Organization,
OrganizationApiKey, OrganizationId, User,
},
},
mail,
util::parse_date,
};
use super::events::{EventRange, get_continuation_token};
pub fn routes() -> Vec<Route> {
routes![ldap_import]
routes![ldap_import, get_events]
}
#[derive(Deserialize)]
@ -196,6 +200,40 @@ async fn ldap_import(data: Json<OrgImportData>, token: PublicToken, conn: DbConn
Ok(())
}
// Upstream: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/AdminConsole/Public/Controllers/EventsController.cs
// Exposes the organization event log to an organization-scoped API client. The
// same data is otherwise only reachable through the internal API, which requires
// an admin user session instead of an organization API key.
#[get("/public/events?<data..>")]
async fn get_events(data: EventRange, token: PublicToken, conn: DbConn) -> JsonResult {
let org_id = token.0;
// Return an empty vec when the org events are disabled.
// This prevents client errors
let events_json: Vec<Value> = if CONFIG.org_events_enabled() {
let start_date = parse_date(&data.start);
let end_date = if let Some(before_date) = &data.continuation_token {
parse_date(before_date)
} else {
parse_date(&data.end)
};
Event::find_by_organization_uuid(&org_id, &start_date, &end_date, &conn)
.await
.iter()
.map(Event::to_json)
.collect()
} else {
Vec::new()
};
Ok(Json(json!({
"object": "list",
"data": events_json,
"continuationToken": get_continuation_token(&events_json),
})))
}
pub struct PublicToken(OrganizationId);
#[rocket::async_trait]

Loading…
Cancel
Save