Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
actions-user committed Oct 2, 2024
2 parents 1549208 + d37335b commit 7acd3d9
Show file tree
Hide file tree
Showing 50 changed files with 1,442 additions and 131 deletions.
2 changes: 2 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 @@ -110,6 +110,7 @@ schemars = "0.8"
sea-orm = "~1.0" # See https://www.sea-ql.org/blog/2024-08-04-sea-orm-1.0/#release-planning
sea-orm-migration = "~1.0"
sea-query = "0.31.0"
semver = "1"
serde = "1.0.183"
serde_json = "1.0.114"
serde_yml = "0.0.12"
Expand Down
21 changes: 21 additions & 0 deletions common/src/db/func.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use migration::Iden;
use sea_orm::{ConnectionTrait, DbErr, ExecResult};
use sea_query::{Func, SelectStatement};
use std::fmt::Write;

/// PostgreSQL's `array_agg` function.
Expand Down Expand Up @@ -52,3 +54,22 @@ impl Iden for VersionMatches {
write!(s, "version_matches").unwrap()
}
}

/// The function updating the deprecated state of a consistent set of advisories.
pub struct UpdateDeprecatedAdvisory;

impl Iden for UpdateDeprecatedAdvisory {
fn unquoted(&self, s: &mut dyn Write) {
write!(s, "update_deprecated_advisory").unwrap()
}
}

impl UpdateDeprecatedAdvisory {
pub async fn execute(db: &impl ConnectionTrait, identifier: &str) -> Result<ExecResult, DbErr> {
let stmt = db
.get_database_backend()
.build(SelectStatement::new().expr(Func::cust(Self).arg(identifier)));

db.execute(stmt).await
}
}
8 changes: 8 additions & 0 deletions common/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ impl Id {
result.extend(sha512.map(Id::Sha512));
result
}

/// Get the value of the [`Id::Uuid`] variant, or return `None` if it is another variant.
pub fn try_as_uid(&self) -> Option<Uuid> {
match &self {
Self::Uuid(uuid) => Some(*uuid),
_ => None,
}
}
}

/// Create a filter for an ID
Expand Down
2 changes: 2 additions & 0 deletions entity/src/advisory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct Model {
pub id: Uuid,
#[graphql(name = "name")]
pub identifier: String,
pub version: Option<String>,
pub deprecated: bool,
pub issuer_id: Option<Uuid>,
pub published: Option<OffsetDateTime>,
pub modified: Option<OffsetDateTime>,
Expand Down
3 changes: 3 additions & 0 deletions migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ mod m0000625_alter_qualified_purl_purl_column;
mod m0000630_create_product_version_range;
mod m0000631_alter_product_cpe_key;
mod m0000640_create_product_status;
mod m0000650_alter_advisory_tracking;

pub struct Migrator;

#[async_trait::async_trait]
Expand Down Expand Up @@ -168,6 +170,7 @@ impl MigratorTrait for Migrator {
Box::new(m0000630_create_product_version_range::Migration),
Box::new(m0000631_alter_product_cpe_key::Migration),
Box::new(m0000640_create_product_status::Migration),
Box::new(m0000650_alter_advisory_tracking::Migration),
]
}
}
Expand Down
114 changes: 114 additions & 0 deletions migration/src/m0000650_alter_advisory_tracking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Advisory::Table)
.add_column(ColumnDef::new(Advisory::Version).string().null().to_owned())
.add_column(
ColumnDef::new(Advisory::Deprecated)
.boolean()
.default(false)
.to_owned(),
)
.to_owned(),
)
.await?;

manager
.create_index(
Index::create()
.table(Advisory::Table)
.name(Indexes::ByIdAndVersion.to_string())
.col(Advisory::Identifier)
.col(Advisory::Version)
.to_owned(),
)
.await?;

// create the function, for updating the state

manager
.get_connection()
.execute_unprepared(include_str!(
"m0000650_alter_advisory_tracking/update_deprecated_advisory.sql"
))
.await
.map(|_| ())?;

// create the state of the "deprecated" column, running the function once
manager
.get_connection()
.execute_unprepared(r#"SELECT update_deprecated_advisory();"#)
.await?;

manager
.get_connection()
.execute_unprepared(
r#"
CREATE INDEX not_deprecated ON advisory (id)
WHERE deprecated is not true;
"#,
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.get_connection()
.execute_unprepared(r#"DROP FUNCTION update_deprecated_advisory"#)
.await?;

manager
.drop_index(
Index::drop()
.table(Advisory::Table)
.name(Indexes::NotDeprecated.to_string())
.to_owned(),
)
.await?;

manager
.drop_index(
Index::drop()
.table(Advisory::Table)
.name(Indexes::ByIdAndVersion.to_string())
.to_owned(),
)
.await?;

manager
.alter_table(
Table::alter()
.table(Advisory::Table)
.drop_column(Advisory::Deprecated)
.drop_column(Advisory::Version)
.to_owned(),
)
.await?;

Ok(())
}
}

#[derive(DeriveIden)]
enum Advisory {
Table,
Identifier,
Version,
Deprecated,
}

#[derive(DeriveIden)]
enum Indexes {
ByIdAndVersion,
NotDeprecated,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE OR REPLACE FUNCTION update_deprecated_advisory(identifier_input TEXT DEFAULT NULL)
RETURNS VOID AS
$$
BEGIN
UPDATE advisory
SET deprecated = (id != (SELECT id
FROM advisory a
WHERE a.identifier = COALESCE(identifier_input, advisory.identifier)
ORDER BY a.modified DESC
LIMIT 1))
WHERE identifier = COALESCE(identifier_input, identifier);
END;
$$ LANGUAGE plpgsql;
1 change: 1 addition & 0 deletions modules/fundamental/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jsonpath-rust = { workspace = true }
log = { workspace = true }
packageurl = { workspace = true }
regex = { workspace = true }
semver = { workspace = true }
serde_json = { workspace = true }
sha2 = { workspace = true }
spdx-rs = { workspace = true }
Expand Down
17 changes: 13 additions & 4 deletions modules/fundamental/src/advisory/endpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ mod label;
mod test;

use crate::{
advisory::service::AdvisoryService, purl::service::PurlService, Error, Error::Internal,
advisory::service::AdvisoryService,
endpoints::Deprecation,
purl::service::PurlService,
Error::{self, Internal},
};
use actix_web::{delete, get, http::header, post, web, HttpResponse, Responder};
use config::Config;
Expand Down Expand Up @@ -49,10 +52,10 @@ pub fn configure(config: &mut web::ServiceConfig, db: Database, upload_limit: us
crate::source_document::model::SourceDocument,
trustify_common::advisory::AdvisoryVulnerabilityAssertions,
trustify_common::advisory::Assertion,
trustify_common::purl::Purl,
trustify_common::id::Id,
trustify_entity::labels::Labels,
trustify_common::purl::Purl,
trustify_cvss::cvss3::severity::Severity,
trustify_entity::labels::Labels,
)),
tags()
)]
Expand All @@ -65,6 +68,7 @@ pub struct ApiDoc;
params(
Query,
Paginated,
Deprecation,
),
responses(
(status = 200, description = "Matching vulnerabilities", body = PaginatedAdvisorySummary),
Expand All @@ -76,8 +80,13 @@ pub async fn all(
state: web::Data<AdvisoryService>,
web::Query(search): web::Query<Query>,
web::Query(paginated): web::Query<Paginated>,
web::Query(Deprecation { deprecated }): web::Query<Deprecation>,
) -> actix_web::Result<impl Responder> {
Ok(HttpResponse::Ok().json(state.fetch_advisories(search, paginated, ()).await?))
Ok(HttpResponse::Ok().json(
state
.fetch_advisories(search, paginated, deprecated, ())
.await?,
))
}

#[utoipa::path(
Expand Down
10 changes: 8 additions & 2 deletions modules/fundamental/src/advisory/endpoints/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async fn all_advisories(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
&Digests::digest("RHSA-1"),
AdvisoryInformation {
title: Some("RHSA-1".to_string()),
version: None,
issuer: None,
published: Some(OffsetDateTime::now_utc()),
modified: None,
Expand Down Expand Up @@ -70,6 +71,7 @@ async fn all_advisories(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
&Digests::digest("RHSA-2"),
AdvisoryInformation {
title: Some("RHSA-2".to_string()),
version: None,
issuer: None,
published: Some(OffsetDateTime::now_utc()),
modified: None,
Expand Down Expand Up @@ -117,6 +119,7 @@ async fn one_advisory(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
&Digests::digest("RHSA-1"),
AdvisoryInformation {
title: Some("RHSA-1".to_string()),
version: None,
issuer: Some("Red Hat Product Security".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
Expand All @@ -134,6 +137,7 @@ async fn one_advisory(ctx: &TrustifyContext) -> Result<(), anyhow::Error> {
&Digests::digest("RHSA-2"),
AdvisoryInformation {
title: Some("RHSA-2".to_string()),
version: None,
issuer: Some("Red Hat Product Security".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
Expand Down Expand Up @@ -210,6 +214,7 @@ async fn one_advisory_by_uuid(ctx: &TrustifyContext) -> Result<(), anyhow::Error
&Digests::digest("RHSA-1"),
AdvisoryInformation {
title: Some("RHSA-1".to_string()),
version: None,
issuer: Some("Red Hat Product Security".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
Expand All @@ -227,6 +232,7 @@ async fn one_advisory_by_uuid(ctx: &TrustifyContext) -> Result<(), anyhow::Error
&Digests::digest("RHSA-1"),
AdvisoryInformation {
title: Some("RHSA-2".to_string()),
version: None,
issuer: Some("Red Hat Product Security".to_string()),
published: Some(OffsetDateTime::now_utc()),
modified: None,
Expand Down Expand Up @@ -337,7 +343,7 @@ async fn upload_default_csaf_format(ctx: &TrustifyContext) -> Result<(), anyhow:
let result: IngestResult = app.call_and_read_body_json(request).await;
log::debug!("{result:?}");
assert!(matches!(result.id, Id::Uuid(_)));
assert_eq!(result.document_id, "CVE-2023-33201");
assert_eq!(result.document_id, "https://www.redhat.com/#CVE-2023-33201");

Ok(())
}
Expand Down Expand Up @@ -415,7 +421,7 @@ async fn upload_with_labels(ctx: &TrustifyContext) -> Result<(), anyhow::Error>
let result: IngestResult = app.call_and_read_body_json(request).await;
log::debug!("{result:?}");
assert!(matches!(result.id, Id::Uuid(_)));
assert_eq!(result.document_id, "CVE-2023-33201");
assert_eq!(result.document_id, "https://www.redhat.com/#CVE-2023-33201");

// now check the labels

Expand Down
2 changes: 1 addition & 1 deletion modules/fundamental/src/advisory/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use trustify_common::memo::Memo;
use trustify_entity::{advisory, labels::Labels, organization};
use utoipa::ToSchema;

#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema, PartialEq, Eq)]
pub struct AdvisoryHead {
/// The opaque UUID of the advisory.
#[serde(with = "uuid::serde::urn")]
Expand Down
Loading

0 comments on commit 7acd3d9

Please sign in to comment.