Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

commands: add get audit events command #243

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
201 changes: 201 additions & 0 deletions api/src/resources/audit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
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 event = self.response.audit_events.get(self.index)?;

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;

self.index += 1;

Some(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,
})
}
}
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
Loading