Skip to content

Commit

Permalink
feat: allow locating by CPE
Browse files Browse the repository at this point in the history
  • Loading branch information
ctron committed Jan 17, 2025
1 parent ddfde89 commit 0693b5c
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 22 deletions.
77 changes: 75 additions & 2 deletions common/src/cpe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,78 @@ use cpe::{
cpe::Cpe as _,
uri::{OwnedUri, Uri},
};
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Cow;
use std::{
fmt::{Debug, Display, Formatter},
str::FromStr,
};
use utoipa::openapi::{KnownFormat, ObjectBuilder, RefOr, Schema, SchemaFormat, Type};
use utoipa::{PartialSchema, ToSchema};
use uuid::Uuid;

#[derive(Clone, Hash, Eq, PartialEq)]
pub struct Cpe {
uri: OwnedUri,
}

impl ToSchema for Cpe {
fn name() -> Cow<'static, str> {
"Cpe".into()
}
}

impl PartialSchema for Cpe {
fn schema() -> RefOr<Schema> {
ObjectBuilder::new()
.schema_type(Type::String)
.format(Some(SchemaFormat::KnownFormat(KnownFormat::Uri)))
.into()
}
}

impl Display for Cpe {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.uri, f)
}
}

impl Serialize for Cpe {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}

impl<'de> Deserialize<'de> for Cpe {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(CpeVisitor)
}
}

struct CpeVisitor;

impl Visitor<'_> for CpeVisitor {
type Value = Cpe;

fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a CPE")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
v.try_into().map_err(Error::custom)
}
}

const NAMESPACE: Uuid = Uuid::from_bytes([
0x1b, 0xf1, 0x2a, 0xd5, 0x0d, 0x67, 0x41, 0x18, 0xa1, 0x38, 0xb8, 0x9f, 0x19, 0x35, 0xe0, 0xa7,
]);
Expand Down Expand Up @@ -172,6 +229,22 @@ impl FromStr for Cpe {
}
}

impl TryFrom<&str> for Cpe {
type Error = <OwnedUri as FromStr>::Err;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Self {
uri: OwnedUri::from_str(value)?,
})
}
}

impl TryFrom<String> for Cpe {
type Error = <OwnedUri as FromStr>::Err;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_str().try_into()
}
}

pub trait CpeCompare: cpe::cpe::Cpe {
fn is_superset<O: CpeCompare>(&self, other: &O) -> bool {
self.compare(other).superset()
Expand Down
1 change: 1 addition & 0 deletions common/src/purl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ impl FromStr for Purl {
.map_err(PurlErr::Package)
}
}

impl<'de> Deserialize<'de> for Purl {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
Expand Down
8 changes: 7 additions & 1 deletion modules/fundamental/src/ai/service/tools/package_info.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::sbom::model::SbomExternalPackageReference;
use crate::{ai::service::tools, purl::service::PurlService, sbom::service::SbomService};
use async_trait::async_trait;
use langchain_rust::tools::Tool;
Expand Down Expand Up @@ -127,7 +128,12 @@ Input: The package name, its Identifier URI, or UUID.
};

let sboms = sbom_service
.find_related_sboms(item.head.uuid, Default::default(), Default::default(), db)
.find_related_sboms(
SbomExternalPackageReference::Purl(item.head.purl.clone()),
Default::default(),
Default::default(),
db,
)
.await?;

#[derive(Serialize)]
Expand Down
41 changes: 29 additions & 12 deletions modules/fundamental/src/sbom/endpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod label;
#[cfg(test)]
mod test;

use crate::sbom::model::SbomExternalPackageReference;
use crate::{
purl::service::PurlService,
sbom::{
Expand All @@ -18,8 +19,7 @@ use actix_http::body::BoxBody;
use actix_web::{delete, get, http::header, post, web, HttpResponse, Responder, ResponseError};
use config::Config;
use futures_util::TryStreamExt;
use sea_orm::prelude::Uuid;
use sea_orm::TransactionTrait;
use sea_orm::{prelude::Uuid, TransactionTrait};
use std::{
fmt::{Display, Formatter},
str::FromStr,
Expand All @@ -30,6 +30,7 @@ use trustify_auth::{
authorizer::{Authorizer, Require},
CreateSbom, DeleteSbom, Permission, ReadAdvisory, ReadSbom,
};
use trustify_common::cpe::Cpe;
use trustify_common::{
db::{query::Query, Database},
decompress::decompress_async,
Expand Down Expand Up @@ -106,9 +107,12 @@ struct AllRelatedQuery {
/// Find by PURL
#[serde(default)]
pub purl: Option<Purl>,
/// Find by CPE
#[serde(default)]
pub cpe: Option<Cpe>,
/// Find by an ID of a package
#[serde(default)]
pub id: Option<Uuid>,
pub id: Option<String>,
}

#[derive(Debug)]
Expand All @@ -118,8 +122,8 @@ impl Display for AllRelatedQueryParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Requires either `purl` or `id` (got - purl: {:?}, id: {:?})",
self.0.purl, self.0.id
"Requires either `purl`, `cpe`, or `id` (got - purl: {:?}, cpe: {:?}, id: {:?})",
self.0.purl, self.0.cpe, self.0.id
)
}
}
Expand All @@ -128,22 +132,35 @@ impl ResponseError for AllRelatedQueryParseError {
fn error_response(&self) -> HttpResponse<BoxBody> {
HttpResponse::BadRequest().json(ErrorInformation {
error: "IdOrPurl".into(),
message: "Requires either `purl` or `id`".to_string(),
message: "Requires either `purl`, `cpe`, or `id`".to_string(),
details: Some(format!(
"Received - PURL: {:?}, ID: {:?}",
self.0.purl, self.0.id
"Received - PURL: {:?}, CPE: {:?}, ID: {:?}",
self.0.purl, self.0.cpe, self.0.id
)),
})
}
}

impl TryFrom<AllRelatedQuery> for Uuid {
impl TryFrom<AllRelatedQuery> for SbomExternalPackageReference {
type Error = AllRelatedQueryParseError;

fn try_from(value: AllRelatedQuery) -> Result<Self, Self::Error> {
Ok(match (&value.purl, &value.id) {
(Some(purl), None) => purl.qualifier_uuid(),
(None, Some(id)) => *id,
Ok(match value {
AllRelatedQuery {
purl: Some(purl),
cpe: None,
id: None,
} => SbomExternalPackageReference::Purl(purl),
AllRelatedQuery {
purl: None,
cpe: Some(cpe),
id: None,
} => SbomExternalPackageReference::Cpe(cpe),
AllRelatedQuery {
purl: None,
cpe: None,
id: Some(id),
} => SbomExternalPackageReference::Id(id),
_ => {
return Err(AllRelatedQueryParseError(value));
}
Expand Down
14 changes: 14 additions & 0 deletions modules/fundamental/src/sbom/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use async_graphql::SimpleObject;
use sea_orm::{prelude::Uuid, ConnectionTrait, ModelTrait, PaginatorTrait};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use trustify_common::cpe::Cpe;
use trustify_common::model::Paginated;
use trustify_common::purl::Purl;
use trustify_entity::{
labels::Labels, relationship::Relationship, sbom, sbom_node, sbom_package, source_document,
};
Expand Down Expand Up @@ -151,3 +153,15 @@ pub enum Which {
/// Target side
Right,
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub enum SbomExternalPackageReference {
/// The ID of the package/component.
///
/// This is actually not internal, but external.
Id(String),
/// The PackageURL of the package/component.
Purl(Purl),
/// The CPE of the package/component.
Cpe(Cpe),
}
24 changes: 17 additions & 7 deletions modules/fundamental/src/sbom/service/sbom.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::SbomService;
use crate::sbom::model::SbomExternalPackageReference;
use crate::{
purl::model::summary::purl::PurlSummary,
sbom::model::{
Expand Down Expand Up @@ -257,17 +258,26 @@ impl SbomService {
#[instrument(skip(self, connection), err(level=tracing::Level::INFO))]
pub async fn find_related_sboms<C: ConnectionTrait>(
&self,
qualified_package_id: Uuid,
external_package_ref: SbomExternalPackageReference,
paginated: Paginated,
query: Query,
connection: &C,
) -> Result<PaginatedResults<SbomSummary>, Error> {
let query = sbom::Entity::find()
.join(JoinType::Join, sbom::Relation::Packages.def())
.join(JoinType::Join, sbom_package::Relation::Purl.def())
.filter(sbom_package_purl_ref::Column::QualifiedPurlId.eq(qualified_package_id))
.filtering(query)?
.find_also_linked(SbomNodeLink);
let select = sbom::Entity::find().join(JoinType::Join, sbom::Relation::Packages.def());

let select = match external_package_ref {
SbomExternalPackageReference::Purl(purl) => select
.join(JoinType::Join, sbom_package::Relation::Purl.def())
.filter(sbom_package_purl_ref::Column::QualifiedPurlId.eq(purl.qualifier_uuid())),
SbomExternalPackageReference::Id(id) => {
select.filter(sbom_package::Column::NodeId.eq(id))
}
SbomExternalPackageReference::Cpe(cpe) => select
.join(JoinType::Join, sbom_package::Relation::Cpe.def())
.filter(sbom_package_cpe_ref::Column::CpeId.eq(cpe.uuid())),
};

let query = select.filtering(query)?.find_also_linked(SbomNodeLink);

// limit and execute

Expand Down

0 comments on commit 0693b5c

Please sign in to comment.