diff --git a/modules/exploit-intelligence/src/endpoints/mod.rs b/modules/exploit-intelligence/src/endpoints/mod.rs index b9e1747ef..3d898fe89 100644 --- a/modules/exploit-intelligence/src/endpoints/mod.rs +++ b/modules/exploit-intelligence/src/endpoints/mod.rs @@ -75,6 +75,13 @@ pub async fn analyze( ) -> Result { ei_service.runtime().ok_or(Error::Unavailable)?; + let vulnerability_id = body.vulnerability_id.trim(); + if vulnerability_id.is_empty() { + return Err(Error::BadRequest( + "vulnerability_id must not be empty".into(), + )); + } + let tx = db_rw.begin().await?; let sbom = sbom::Entity::find() @@ -87,7 +94,7 @@ pub async fn analyze( } match ei_service - .create_job(body.sbom_id, &body.vulnerability_id, &tx) + .create_job(body.sbom_id, vulnerability_id, &tx) .await { Ok(job) => { @@ -101,7 +108,7 @@ pub async fn analyze( drop(tx); let ro_tx = db_ro.begin().await?; let active = ei_service - .find_active_job(body.sbom_id, &body.vulnerability_id, &ro_tx) + .find_active_job(body.sbom_id, vulnerability_id, &ro_tx) .await?; match active { Some(job) => Ok(HttpResponse::Ok().json(AnalyzeResponse { diff --git a/modules/exploit-intelligence/src/endpoints/test.rs b/modules/exploit-intelligence/src/endpoints/test.rs index 0c9c1cd94..d7eced0ce 100644 --- a/modules/exploit-intelligence/src/endpoints/test.rs +++ b/modules/exploit-intelligence/src/endpoints/test.rs @@ -14,7 +14,7 @@ use sea_orm::{ActiveModelTrait, Set, TransactionTrait}; use test_context::test_context; use test_log::test; use time::OffsetDateTime; -use trustify_common::{db, model::PaginatedResults}; +use trustify_common::{db, error::ErrorInformation, model::PaginatedResults}; use trustify_entity::{ exploit_intelligence_job::{self, ExploitIntelligenceFinding, ExploitIntelligenceJobStatus}, exploit_intelligence_job_component, @@ -743,6 +743,49 @@ async fn analyze_sbom_not_found(ctx: &TrustifyContext) -> anyhow::Result<()> { Ok(()) } +/// Verifies that POSTing an analyze request with an empty or whitespace-only +/// vulnerability_id returns 400 Bad Request. +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn analyze_rejects_blank_vulnerability_id(ctx: &TrustifyContext) -> anyhow::Result<()> { + let ei_service = test_service(); + let db_rw = db::ReadWrite::new(ctx.db.clone()); + let db_ro = db::ReadOnly::new(ctx.db.clone()); + + let app = test::init_service( + App::new() + .add_test_authorizer() + .app_data(web::Data::new(db_rw)) + .app_data(web::Data::new(db_ro)) + .app_data(web::Data::new(ei_service)) + .service(super::analyze), + ) + .await; + + for blank in ["", " "] { + let req = TestRequest::post() + .uri("/v3/exploit-intelligence/analyze") + .set_json(AnalyzeRequest { + sbom_id: Uuid::now_v7(), + vulnerability_id: blank.to_string(), + }) + .to_request(); + + let resp = test::call_service(&app, req).await; + assert_eq!( + resp.status(), + 400, + "expected 400 for vulnerability_id={blank:?}" + ); + + let body: ErrorInformation = test::read_body_json(resp).await; + assert_eq!(body.error, "BadRequest"); + assert_eq!(body.message, "vulnerability_id must not be empty"); + } + + Ok(()) +} + /// Verifies that POSTing an analyze request when Exploit Intelligence is not /// configured returns 503 Service Unavailable. #[test_context(TrustifyContext)] diff --git a/modules/exploit-intelligence/src/error.rs b/modules/exploit-intelligence/src/error.rs index 3ba66e908..392e78bbe 100644 --- a/modules/exploit-intelligence/src/error.rs +++ b/modules/exploit-intelligence/src/error.rs @@ -9,6 +9,8 @@ use trustify_common::{ pub enum Error { #[error("unavailable")] Unavailable, + #[error("bad request: {0}")] + BadRequest(String), #[error("not found: {0}")] NotFound(String), #[error("database error: {0}")] @@ -57,6 +59,9 @@ impl ResponseError for Error { Self::Unavailable => { HttpResponse::ServiceUnavailable().json(ErrorInformation::new("Unavailable", self)) } + Self::BadRequest(msg) => { + HttpResponse::BadRequest().json(ErrorInformation::new("BadRequest", msg)) + } Self::NotFound(msg) => { HttpResponse::NotFound().json(ErrorInformation::new("NotFound", msg)) } diff --git a/modules/exploit-intelligence/src/runner/mod.rs b/modules/exploit-intelligence/src/runner/mod.rs index b706c56cc..e07f790e9 100644 --- a/modules/exploit-intelligence/src/runner/mod.rs +++ b/modules/exploit-intelligence/src/runner/mod.rs @@ -65,9 +65,10 @@ impl From for AnalysisError { fn from(e: crate::Error) -> Self { match &e { crate::Error::Database(_) | crate::Error::Any(_) => Self::Retryable(e.into()), - crate::Error::Unavailable | crate::Error::NotFound(_) | crate::Error::Query(_) => { - Self::Permanent(e.into()) - } + crate::Error::Unavailable + | crate::Error::BadRequest(_) + | crate::Error::NotFound(_) + | crate::Error::Query(_) => Self::Permanent(e.into()), } } }