Skip to content
Open
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
47 changes: 47 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ actix-web = "4.3.1"
actix-web-extras = "0.1"
actix-web-httpauth = "0.8"
actix-web-static-files = "4.0.1"
actix-multipart = "0.7.2"
anyhow = "1.0.72"
async-compression = "0.4.13"
async-graphql = "7.0.5"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ CPE (Product?) and/or pURLs described by the SBOM

## Related Projects

* [Trustify user interface](https://github.com/trustification/trustify-ui)
* [Trustify user interface](https://github.com/guacsec/trustify-ui)
* [Helm charts used for the Trustify deployment](https://github.com/trustification/trustify-helm-charts)
* [OpenShift Operator used for the Trustify deployment](https://github.com/trustification/trustify-operator)
* [Ansible playbooks used for the Trustify deployment](https://github.com/trustification/trustify-ansible)
Expand Down
7 changes: 4 additions & 3 deletions docs/book/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions modules/ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ trustify-module-ingestor = { workspace = true }

actix-web = { workspace = true }
actix-web-static-files = { workspace = true }
actix-multipart = { workspace = true }
anyhow = { workspace = true }
serde = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde-cyclonedx = { workspace = true }
serde_json = { workspace = true }
spdx-rs = { workspace = true }
thiserror = { workspace = true }
tokio = {workspace = true }
tokio = { workspace = true }
trustify-ui = { workspace = true }
utoipa = { workspace = true }
utoipa-actix-web = { workspace = true }
flate2 = { workspace = true }
tar = { workspace = true }

[dev-dependencies]
test-log = { workspace = true }
Expand Down
99 changes: 94 additions & 5 deletions modules/ui/src/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,38 @@ use crate::{
model::ExtractResult,
service::{extract_cyclonedx_purls, extract_spdx_purls},
};
use actix_multipart::form::{MultipartForm, json::Json as MPJson};
use actix_web::{
HttpResponse, Responder,
http::header,
post,
web::{self, Bytes, ServiceConfig},
};
use actix_web_static_files::{ResourceFiles, deps::static_files::Resource};
use std::collections::HashMap;
use flate2::{Compression, write::GzEncoder};
use std::{collections::HashMap, sync::Arc, time::Duration};
use tar::{Builder, Header};
use trustify_common::{decompress::decompress_async, error::ErrorInformation, model::BinaryData};
use trustify_module_ingestor::service::Format;
use trustify_ui::{UI, trustify_ui};
use utoipa::IntoParams;
use utoipa::{IntoParams, ToSchema};

#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct Config {
/// Upload limit for scan (after decompression).
pub scan_limit: usize,
}

pub fn post_configure(svc: &mut ServiceConfig, ui: &UiResources) {
svc.service(ResourceFiles::new("/", ui.resources()).resolve_not_found_to(""));
pub fn post_configure(svc: &mut ServiceConfig, ui: Arc<UiResources>) {
let resources = ui.resources();
svc.app_data(web::Data::new(ui))
.service(ResourceFiles::new("/", resources).resolve_not_found_to(""));
}

pub fn configure(svc: &mut utoipa_actix_web::service_config::ServiceConfig, config: Config) {
svc.app_data(web::Data::new(config))
.service(extract_sbom_purls);
.service(extract_sbom_purls)
.service(generate_sbom_static_report);
}

pub struct UiResources {
Expand Down Expand Up @@ -136,3 +142,86 @@ async fn extract_sbom_purls(
warnings,
}))
}

#[derive(Debug, MultipartForm, ToSchema)]
struct UploadForm {
#[schema(value_type = Object)]
analysis_response: MPJson<serde_json::Value>,
}

#[utoipa::path(
tag = "ui",
operation_id = "generateSbomStaticReport",
request_body(content = UploadForm, content_type = "multipart/form-data"),
responses(
(
status = 200,
description = "Static report",
body = Vec<u8>,
content_type = "application/gzip"
),
(
status = 400,
description = "Bad request data, like an unsupported format or invalid data",
body = ErrorInformation,
)
)
)]
#[post("/v2/ui/generate-sbom-static-report")]
/// Generates an static report
async fn generate_sbom_static_report(
ui: web::Data<Arc<UiResources>>,
MultipartForm(form): MultipartForm<UploadForm>,
) -> Result<impl Responder, Error> {
let mut data = Vec::new();
{
let encoder = GzEncoder::new(&mut data, Compression::default());
let mut gzip = Builder::new(encoder);

// Add static report template
let prefix = "static-report/";
for (path, resource) in ui.resources.iter() {
if let Some(relative_path) = path.strip_prefix(prefix) {
let mut header = Header::new_gnu();
header.set_size(resource.data.len() as u64);
header.set_mode(0o644);
header.set_cksum();
header.set_mtime(
std::time::UNIX_EPOCH
.elapsed()
.unwrap_or(Duration::from_secs(0))
.as_secs(),
);

gzip.append_data(&mut header, relative_path, resource.data)?;
}
}

// Add static report data
let json_data = serde_json::to_string(&form.analysis_response.0)?;
let js_data = format!("window.analysis_response={json_data}");

let mut header = Header::new_gnu();
header.set_size(js_data.len() as u64);
header.set_mode(0o644);
header.set_cksum();
header.set_mtime(
std::time::UNIX_EPOCH
.elapsed()
.unwrap_or(Duration::from_secs(0))
.as_secs(),
);
gzip.append_data(&mut header, "data.js", js_data.as_bytes())?;

// Close gzip
gzip.finish()?;
}

Ok(HttpResponse::Ok()
.content_type("application/gzip")
.append_header((
"Content-Disposition",
"attachment; filename=\"static-report.tar.gz\"",
))
.body(data))
}
2 changes: 2 additions & 0 deletions modules/ui/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub enum Error {
BadRequest(String, Option<String>),
#[error(transparent)]
Decompression(#[from] trustify_common::decompress::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
}

impl actix_web::error::ResponseError for Error {
Expand Down
4 changes: 3 additions & 1 deletion modules/ui/tests/extract.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![allow(clippy::expect_used)]

use std::sync::Arc;

use actix_web::{dev::ServiceResponse, test::TestRequest};
use serde_json::{Value, json};
use test_log::test;
Expand All @@ -17,7 +19,7 @@ async fn caller_with(config: Config) -> anyhow::Result<impl CallService> {
call::caller(move |svc| {
configure(svc, config);
svc.map(|svc| {
post_configure(svc, &ui);
post_configure(svc, Arc::new(ui));
svc
});
})
Expand Down
36 changes: 36 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2848,6 +2848,35 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorInformation'
/api/v2/ui/generate-sbom-static-report:
post:
tags:
- ui
summary: Generates an static report
operationId: generateSbomStaticReport
requestBody:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/UploadForm'
required: true
responses:
'200':
description: Static report
content:
application/gzip:
schema:
type: array
items:
type: integer
format: int32
minimum: 0
'400':
description: Bad request data, like an unsupported format or invalid data
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorInformation'
/api/v2/userPreference/{key}:
get:
tags:
Expand Down Expand Up @@ -5138,6 +5167,13 @@ components:
oneOf:
- type: 'null'
- type: string
UploadForm:
type: object
required:
- analysis_response
properties:
analysis_response:
type: object
VersionedPurlHead:
type: object
required:
Expand Down
2 changes: 1 addition & 1 deletion server/src/profile/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ fn post_configure(svc: &mut web::ServiceConfig, config: PostConfig) {
svc.configure(|svc| {
// I think the UI must come last due to
// its use of `resolve_not_found_to`
trustify_module_ui::endpoints::post_configure(svc, &ui);
trustify_module_ui::endpoints::post_configure(svc, ui);
});
}

Expand Down