Skip to content

Commit

Permalink
commands: add get audit events command
Browse files Browse the repository at this point in the history
  • Loading branch information
joe-prosser committed Nov 16, 2023
1 parent 084498c commit 3900092
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 4 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
## Unreleased

- Add get audit events command

## v0.21.1

- Add more stream stats

## v0.21.0

- Fix url used for fetching streams
- Return `is_end_sequence` on stream fetch
- Make `transform_tag` optional on `create bucket`
Expand Down
27 changes: 27 additions & 0 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use reqwest::{
IntoUrl, Proxy, Result as ReqwestResult,
};
use resources::{
comment::CommentTimestampFilter,
dataset::{
QueryRequestParams, QueryResponse,
StatisticsRequestParams as DatasetStatisticsRequestParams, SummaryRequestParams,
Expand All @@ -33,6 +34,7 @@ use std::{cell::Cell, fmt::Display, path::Path};
use url::Url;

use crate::resources::{
audit::{AuditQueryFilter, AuditQueryRequest, AuditQueryResponse},
bucket::{
CreateRequest as CreateBucketRequest, CreateResponse as CreateBucketResponse,
GetAvailableResponse as GetAvailableBucketsResponse, GetResponse as GetBucketResponse,
Expand Down Expand Up @@ -408,6 +410,27 @@ impl Client {
)
}

pub fn get_audit_events(
&self,
minimum_timestamp: Option<DateTime<Utc>>,
maximum_timestamp: Option<DateTime<Utc>>,
continuation: Option<Continuation>,
) -> Result<AuditQueryResponse> {
self.post::<_, _, AuditQueryResponse>(
self.endpoints.audit_events_query()?,
AuditQueryRequest {
continuation,
filter: AuditQueryFilter {
timestamp: CommentTimestampFilter {
minimum: minimum_timestamp,
maximum: maximum_timestamp,
},
},
},
Retry::Yes,
)
}

pub fn get_validation(
&self,
dataset_name: &DatasetFullName,
Expand Down Expand Up @@ -1314,6 +1337,10 @@ impl Endpoints {
})
}

fn audit_events_query(&self) -> Result<Url> {
construct_endpoint(&self.base, &["api", "v1", "audit_events", "query"])
}

fn validation(
&self,
dataset_name: &DatasetFullName,
Expand Down
209 changes: 209 additions & 0 deletions api/src/resources/audit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
use chrono::{DateTime, Utc};

use crate::{Continuation, DatasetId, DatasetName, Email, ProjectName, UserId, Username};

use super::{comment::CommentTimestampFilter, project::Id as ProjectId};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuditQueryFilter {
pub timestamp: CommentTimestampFilter,
}

#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
pub struct AuditEventId(pub String);

#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
pub struct AuditEventType(pub String);

#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
pub struct AuditTenantName(pub String);

#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
pub struct AuditTenantId(pub String);

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuditQueryRequest {
pub filter: AuditQueryFilter,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub continuation: Option<Continuation>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuditEvent {
actor_user_id: UserId,
actor_tenant_id: AuditTenantId,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
dataset_ids: Vec<DatasetId>,
event_id: AuditEventId,
event_type: AuditEventType,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
project_ids: Vec<ProjectId>,
tenant_ids: Vec<AuditTenantId>,
timestamp: DateTime<Utc>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PrintableAuditEvent {
pub actor_email: Email,
pub actor_tenant_name: AuditTenantName,
pub event_type: AuditEventType,
pub dataset_names: Vec<DatasetName>,
pub event_id: AuditEventId,
pub project_names: Vec<ProjectName>,
pub tenant_names: Vec<AuditTenantName>,
pub timestamp: DateTime<Utc>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
struct AuditDataset {
id: DatasetId,
name: DatasetName,
project_id: ProjectId,
title: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
struct AuditProject {
id: ProjectId,
name: ProjectName,
tenant_id: AuditTenantId,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
struct AuditTenant {
id: AuditTenantId,
name: AuditTenantName,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
struct AuditUser {
display_name: Username,
email: Email,
id: UserId,
tenant_id: AuditTenantId,
username: Username,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuditQueryResponse {
audit_events: Vec<AuditEvent>,
projects: Vec<AuditProject>,
pub continuation: Option<Continuation>,
datasets: Vec<AuditDataset>,
tenants: Vec<AuditTenant>,
users: Vec<AuditUser>,
}

impl AuditQueryResponse {
pub fn into_iter_printable(self) -> QueryResponseIterator {
QueryResponseIterator {
response: self,
index: 0,
}
}

fn get_user(&self, user_id: &UserId) -> Option<&AuditUser> {
self.users.iter().find(|user| user.id == *user_id)
}

fn get_dataset(&self, dataset_id: &DatasetId) -> Option<&AuditDataset> {
self.datasets
.iter()
.find(|dataset| dataset.id == *dataset_id)
}

fn get_project(&self, project_id: &ProjectId) -> Option<&AuditProject> {
self.projects
.iter()
.find(|project| project.id == *project_id)
}

fn get_tenant(&self, tenant_id: &AuditTenantId) -> Option<&AuditTenant> {
self.tenants.iter().find(|tenant| tenant.id == *tenant_id)
}
}

pub struct QueryResponseIterator {
response: AuditQueryResponse,
index: usize,
}

impl Iterator for QueryResponseIterator {
type Item = PrintableAuditEvent;
fn next(&mut self) -> Option<Self::Item> {
let response = self.response.audit_events.get(self.index).map(|event| {
let actor_email = &self
.response
.get_user(&event.actor_user_id)
.unwrap_or_else(|| panic!("Could not find user for id `{}`", event.actor_user_id.0))
.email;

let dataset_names = event
.dataset_ids
.iter()
.map(|dataset_id| {
&self
.response
.get_dataset(dataset_id)
.unwrap_or_else(|| {
panic!("Could not get dataset for id `{}`", dataset_id.0)
})
.name
})
.cloned()
.collect();

let project_names = event
.project_ids
.iter()
.map(|project_id| {
&self
.response
.get_project(project_id)
.unwrap_or_else(|| {
panic!("Could not get project for id `{}`", project_id.0)
})
.name
})
.cloned()
.collect();

let tenant_names = event
.tenant_ids
.iter()
.map(|tenant_id| {
&self
.response
.get_tenant(tenant_id)
.unwrap_or_else(|| panic!("Could not get tenant for id `{}`", tenant_id.0))
.name
})
.cloned()
.collect();

let actor_tenant_name = &self
.response
.get_tenant(&event.actor_tenant_id)
.unwrap_or_else(|| {
panic!("Could not get tenant for id `{}`", event.actor_tenant_id.0)
})
.name;

PrintableAuditEvent {
event_type: event.event_type.clone(),
actor_tenant_name: actor_tenant_name.clone(),
event_id: event.event_id.clone(),
timestamp: event.timestamp,
actor_email: actor_email.clone(),
dataset_names,
project_names,
tenant_names,
}
});

self.index += 1;

response
}
}
1 change: 1 addition & 0 deletions api/src/resources/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod audit;
pub mod bucket;
pub mod comment;
pub mod dataset;
Expand Down
7 changes: 4 additions & 3 deletions api/src/resources/tenant_id.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::{fmt::Display, str::FromStr};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct ReinferTenantId(String);

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct UiPathTenantId(String);

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TenantId {
Reinfer(ReinferTenantId),
UiPath(UiPathTenantId),
Expand Down
47 changes: 47 additions & 0 deletions cli/src/commands/get/audit_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use anyhow::Result;
use chrono::{DateTime, Utc};
use log::info;
use reinfer_client::{resources::audit::PrintableAuditEvent, Client};
use structopt::StructOpt;

use crate::printer::Printer;

#[derive(Debug, StructOpt)]
pub struct GetAuditEventsArgs {
#[structopt(short = "m", long = "minimum")]
/// Minimum Timestamp for audit events
minimum_timestamp: Option<DateTime<Utc>>,

#[structopt(short = "M", long = "maximum")]
/// Maximum Timestamp for audit events
maximum_timestamp: Option<DateTime<Utc>>,
}

pub fn get(client: &Client, args: &GetAuditEventsArgs, printer: &Printer) -> Result<()> {
let GetAuditEventsArgs {
minimum_timestamp,
maximum_timestamp,
} = args;

let mut continuation = None;

let mut all_printable_events = Vec::new();

loop {
let audit_events =
client.get_audit_events(*minimum_timestamp, *maximum_timestamp, continuation)?;
let mut printable_events: Vec<PrintableAuditEvent> =
audit_events.clone().into_iter_printable().collect();

all_printable_events.append(&mut printable_events);

if audit_events.continuation.is_none() {
break;
} else {
info!("Downloaded {} events", all_printable_events.len());
continuation = audit_events.continuation
}
}

printer.print_resources(all_printable_events.iter())
}
7 changes: 7 additions & 0 deletions cli/src/commands/get/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod audit_events;
mod buckets;
mod comments;
mod datasets;
Expand All @@ -13,6 +14,7 @@ use scoped_threadpool::Pool;
use structopt::StructOpt;

use self::{
audit_events::GetAuditEventsArgs,
buckets::GetBucketsArgs,
comments::{GetManyCommentsArgs, GetSingleCommentArgs},
datasets::GetDatasetsArgs,
Expand Down Expand Up @@ -72,6 +74,10 @@ pub enum GetArgs {
#[structopt(name = "quotas")]
/// List all quotas for current tenant
Quotas,

#[structopt(name = "audit-events")]
/// Get audit events for current tenant
AuditEvents(GetAuditEventsArgs),
}

pub fn run(args: &GetArgs, client: Client, printer: &Printer, pool: &mut Pool) -> Result<()> {
Expand All @@ -88,5 +94,6 @@ pub fn run(args: &GetArgs, client: Client, printer: &Printer, pool: &mut Pool) -
GetArgs::Users(args) => users::get(&client, args, printer),
GetArgs::CurrentUser => users::get_current_user(&client, printer),
GetArgs::Quotas => quota::get(&client, printer),
GetArgs::AuditEvents(args) => audit_events::get(&client, args, printer),
}
}
Loading

0 comments on commit 3900092

Please sign in to comment.