Skip to content

Commit

Permalink
Upload sbom
Browse files Browse the repository at this point in the history
Signed-off-by: carlosthe19916 <[email protected]>
  • Loading branch information
carlosthe19916 committed Jan 16, 2024
1 parent fcac5dc commit bcc8dfa
Show file tree
Hide file tree
Showing 13 changed files with 326 additions and 10 deletions.
29 changes: 26 additions & 3 deletions spog/api/src/app_state.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use crate::error::Error;
use std::sync::Arc;

use async_trait::async_trait;
use bytes::Bytes;
use http::StatusCode;
use std::sync::Arc;
use tracing::instrument;

use trustification_api::search::SearchOptions;
use trustification_api::Apply;
use trustification_auth::client::{TokenInjector, TokenProvider};
use trustification_infrastructure::tracing::PropagateCurrentContext;

use crate::error::Error;

pub struct AppState {
pub client: reqwest::Client,
pub provider: Arc<dyn TokenProvider>,
Expand Down Expand Up @@ -39,6 +43,25 @@ impl AppState {
Ok(response.bytes_stream())
}

#[instrument(skip(self, provider), err)]
pub async fn post_sbom(&self, id: &str, provider: &dyn TokenProvider, data: Bytes) -> Result<(), Error> {
let url = self.bombastic.join("/api/v1/sbom")?;
self.client
.put(url)
.body(data)
.query(&[("id", id)])
.header("content-type", "application/json")
.propagate_current_context()
.inject_token(provider)
.await?
.send()
.await?
.or_status_error()
.await?;

Ok(())
}

#[instrument(skip(self, provider), err)]
pub async fn search_sbom(
&self,
Expand Down Expand Up @@ -153,7 +176,7 @@ pub trait ResponseError: Sized {
#[async_trait]
impl ResponseError for reqwest::Response {
async fn or_status_error(self) -> Result<Self, Error> {
if self.status() == StatusCode::OK {
if self.status().is_success() {
Ok(self)
} else {
let status = self.status();
Expand Down
3 changes: 3 additions & 0 deletions spog/api/src/endpoints/sbom/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod get;
mod post;
mod search;
mod vuln;

pub use get::*;
pub use post::*;
pub use search::*;
pub use vuln::*;

Expand All @@ -25,5 +27,6 @@ pub(crate) fn configure(auth: Option<Arc<Authenticator>>) -> impl FnOnce(&mut Se
);
// the get operation doesn't get the authenticator added, as we check this using the access_token query parameter
config.service(web::resource("/api/v1/sbom").to(get));
config.service(web::resource("/api/v1/sbom/upload").to(post));
}
}
35 changes: 35 additions & 0 deletions spog/api/src/endpoints/sbom/post.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use actix_web::{web, HttpResponse};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use bytes::Bytes;
use tracing::instrument;
use uuid::Uuid;

use crate::app_state::AppState;

#[derive(Debug, serde::Deserialize, utoipa::IntoParams)]
pub struct PostParams {
/// Access token to use for authentication
pub token: Option<String>,
}

#[utoipa::path(
post,
path = "/api/v1/sbom/upload",
responses(
(status = OK, description = "SBOM was uploaded"),
),
params(PostParams)
)]
#[instrument(skip(state, access_token))]
pub async fn post(
data: Bytes,
state: web::Data<AppState>,
web::Query(PostParams { token }): web::Query<PostParams>,
access_token: Option<BearerAuth>,
) -> actix_web::Result<HttpResponse> {
let id = Uuid::new_v4().to_string();

let token = token.or_else(|| access_token.map(|s| s.token().to_string()));
state.post_sbom(&id, &token, data).await?;
Ok(HttpResponse::Ok().body(id))
}
16 changes: 15 additions & 1 deletion spog/ui/crates/backend/src/sbom.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{ApplyAccessToken, Backend, Endpoint};
use reqwest::StatusCode;
use reqwest::{Body, StatusCode};
use spog_model::prelude::{SbomReport, SbomSummary};
use spog_ui_common::error::*;
use std::rc::Rc;
Expand All @@ -23,6 +23,20 @@ impl SBOMService {
}
}

pub async fn upload(&self, data: impl Into<Body>) -> Result<String, ApiError> {
let url = self.backend.join(Endpoint::Api, "/api/v1/sbom/upload")?;

let response = self
.client
.post(url)
.latest_access_token(&self.access_token)
.body(data)
.send()
.await?;

Ok(response.api_error_for_status().await?.text().await?)
}

pub async fn get(&self, id: impl AsRef<str>) -> Result<Option<String>, ApiError> {
let mut url = self.backend.join(Endpoint::Api, "/api/v1/sbom")?;
url.query_pairs_mut().append_pair("id", id.as_ref()).finish();
Expand Down
7 changes: 6 additions & 1 deletion spog/ui/crates/components/src/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ pub fn inline_download(props: &LocalDownloadButtonProperties) -> Html {
let onclick = use_wrap_tracking(
onclick,
(props.r#type.clone(), props.filename.clone()),
|_, (r#type, filename)| ("DetailsPage File Downloaded", json!({"type": r#type, "filename": filename})),
|_, (r#type, filename)| {
(
"DetailsPage File Downloaded",
json!({"type": r#type, "filename": filename}),
)
},
);

let href = use_state_eq::<Option<String>, _>(|| None);
Expand Down
1 change: 1 addition & 0 deletions spog/ui/crates/navigation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum AppRoute {
},
Advisory(View),
Scanner,
Uploader,
Search {
terms: String,
},
Expand Down
2 changes: 2 additions & 0 deletions spog/ui/src/console/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ fn authenticated_page(props: &ChildrenProperties) -> Html {
if config.features.scanner {
<NavRouterItem<AppRoute> to={AppRoute::Scanner}>{ "Scan SBOM" }</NavRouterItem<AppRoute>>
}
<NavRouterItem<AppRoute> to={AppRoute::Uploader}>{ "Upload SBOM" }</NavRouterItem<AppRoute>>
if config.features.extend_section {
<NavExpandable title="Extend">
if let Ok(url) = backend.join(Endpoint::Bombastic, "/swagger-ui/") {
Expand Down Expand Up @@ -224,6 +225,7 @@ fn render(route: AppRoute, config: &spog_model::config::Configuration) -> Html {

AppRoute::Chicken => html!(<pages::Chicken/>),
AppRoute::Scanner if config.features.scanner => html!(<pages::Scanner/>),
AppRoute::Uploader => html!(<pages::Uploader/>),

AppRoute::Sbom(View::Search { query }) if config.features.dedicated_search => {
html!(<pages::Sbom {query} />)
Expand Down
2 changes: 2 additions & 0 deletions spog/ui/src/pages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod sbom_report;
mod sbom_search;
mod scanner;
mod search;
mod uploader;

pub use self::cve::*;
pub use advisory::*;
Expand All @@ -31,3 +32,4 @@ pub use sbom_report::SbomReport;
pub use sbom_search::Sbom;
pub use scanner::*;
pub use search::Search;
pub use uploader::*;
57 changes: 56 additions & 1 deletion spog/ui/src/pages/sbom/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! The SBOM details page
use crate::pages::scanner::report::Report;
use crate::{common::clean_ext, model, pages::sbom_report::SbomReport};
use patternfly_yew::prelude::*;
use spog_ui_backend::use_backend;
use reqwest::Body;
use spog_ui_backend::{use_backend, AnalyzeService};
use spog_ui_common::{config::use_config, error::components::Error};
use spog_ui_components::{
common::{NotFound, PageHeading},
Expand Down Expand Up @@ -85,6 +87,7 @@ fn details(props: &DetailsProps) -> Html {
Info,
Packages,
Source,
Report,
}

let config = use_config();
Expand All @@ -104,6 +107,7 @@ fn details(props: &DetailsProps) -> Html {
{ for config.features.show_source.then(|| html_nested!(
<Tab<TabIndex> index={TabIndex::Source} title="Source" />
)) }
<Tab<TabIndex> index={TabIndex::Report} title="Report" />
</Tabs<TabIndex>>
</PageSection>

Expand Down Expand Up @@ -135,6 +139,9 @@ fn details(props: &DetailsProps) -> Html {
<PageSection hidden={*tab != TabIndex::Source} variant={PageSectionVariant::Light} fill={PageSectionFill::Fill}>
<SourceCode source={source.clone()} />
</PageSection>
<PageSection hidden={*tab != TabIndex::Report} variant={PageSectionVariant::Light} fill={PageSectionFill::Fill}>
<ReportViewwer raw={source.clone()}/>
</PageSection>
</>
)
}
Expand All @@ -148,6 +155,7 @@ fn details(props: &DetailsProps) -> Html {
{ for config.features.show_source.then(|| html_nested!(
<Tab<TabIndex> index={TabIndex::Source} title="Source" />
)) }
<Tab<TabIndex> index={TabIndex::Report} title="Report" />
</Tabs<TabIndex>>
</PageSection>

Expand All @@ -164,6 +172,9 @@ fn details(props: &DetailsProps) -> Html {
<PageSection hidden={*tab != TabIndex::Source} fill={PageSectionFill::Fill}>
<SourceCode source={source.clone()} />
</PageSection>
<PageSection hidden={*tab != TabIndex::Report} fill={PageSectionFill::Fill}>
<ReportViewwer raw={source.clone()}/>
</PageSection>
</>
)
}
Expand Down Expand Up @@ -191,8 +202,52 @@ fn details(props: &DetailsProps) -> Html {
<PageSection hidden={*tab != TabIndex::Source} fill={PageSectionFill::Fill}>
<SourceCode source={source.clone()} />
</PageSection>
<PageSection hidden={*tab != TabIndex::Report} fill={PageSectionFill::Fill}>
<ReportViewwer raw={source.clone()}/>
</PageSection>
</>
)
}
}
}

#[derive(Clone, PartialEq, Properties)]
pub struct ReportViewwerProperties {
pub raw: Rc<String>,
}

#[function_component(ReportViewwer)]
pub fn report_viewer(props: &ReportViewwerProperties) -> Html {
let backend = use_backend();
let access_token = use_latest_access_token();

let fetch = {
use_async_with_cloned_deps(
move |raw| async move {
let service = AnalyzeService::new(backend, access_token);
service.report(Body::from((*raw).clone())).await.map(Rc::new)
},
props.raw.clone(),
)
};

html!(
<>
{
match &*fetch {
UseAsyncState::Pending | UseAsyncState::Processing => html!(
<PageSection fill={PageSectionFill::Fill}>
<Spinner />
</PageSection>
),
UseAsyncState::Ready(Ok(data)) => html!(
<Report data={data.clone()} />
),
UseAsyncState::Ready(Err(_)) => html!(
<Error title="Error" message="Error while generating report" />
)
}
}
</>
)
}
6 changes: 3 additions & 3 deletions spog/ui/src/pages/scanner/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod inspect;
// mod unknown;
mod report;
mod upload;
pub mod report;
pub mod upload;

use analytics_next::TrackingEvent;
use anyhow::bail;
Expand Down Expand Up @@ -56,7 +56,7 @@ fn is_supported_package(purl: &str) -> bool {
}
}

fn parse(data: &[u8]) -> Result<SBOM, anyhow::Error> {
pub fn parse(data: &[u8]) -> Result<SBOM, anyhow::Error> {
let sbom = SBOM::parse(data)?;

#[allow(clippy::single_match)]
Expand Down
5 changes: 4 additions & 1 deletion spog/ui/src/pages/scanner/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ impl std::fmt::Display for DropContent {

#[derive(Clone, PartialEq, Properties)]
pub struct UploadProperties {
#[prop_or("Scan".into())]
pub primary_btn_text: AttrValue,

pub onsubmit: Callback<Rc<String>>,
#[prop_or(default_validate())]
pub onvalidate: Callback<Rc<String>, Result<Rc<String>, String>>,
Expand Down Expand Up @@ -295,7 +298,7 @@ pub fn upload(props: &UploadProperties) -> Html {
disabled={state == InputState::Error}
onclick={onsubmit}
>
{"Scan"}
{&props.primary_btn_text}
</Button>
</FlexItem>
<FlexItem>
Expand Down
Loading

0 comments on commit bcc8dfa

Please sign in to comment.