Skip to content

Commit

Permalink
controllers/krate/publish: Check deleted_crates for availability of…
Browse files Browse the repository at this point in the history
… the crate name
  • Loading branch information
Turbo87 committed Nov 12, 2024
1 parent f0d0afd commit a11076b
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 0 deletions.
17 changes: 17 additions & 0 deletions src/controllers/krate/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::worker::jobs::{
use axum::body::Bytes;
use axum::Json;
use cargo_manifest::{Dependency, DepsSet, TargetDepsSet};
use chrono::{DateTime, SecondsFormat, Utc};
use crates_io_tarball::{process_tarball, TarballError};
use crates_io_worker::BackgroundJob;
use diesel::connection::DefaultLoadingMode;
Expand Down Expand Up @@ -86,6 +87,22 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
let (existing_crate, auth) = {
use diesel_async::RunQueryDsl;

let deleted_crate: Option<(String, DateTime<Utc>)> = deleted_crates::table
.filter(canon_crate_name(deleted_crates::name).eq(canon_crate_name(&metadata.name)))
.filter(deleted_crates::available_at.gt(Utc::now()))
.select((deleted_crates::name, deleted_crates::available_at))
.first(&mut conn)
.await
.optional()?;

if let Some(deleted_crate) = deleted_crate {
return Err(bad_request(format!(
"A crate with the name `{}` was recently deleted. Reuse of this name will be available after {}.",
deleted_crate.0,
deleted_crate.1.to_rfc3339_opts(SecondsFormat::Secs, true)
)));
}

// this query should only be used for the endpoint scope calculation
// since a race condition there would only cause `publish-new` instead of
// `publish-update` to be used.
Expand Down
39 changes: 39 additions & 0 deletions src/tests/krate/publish/deleted_crates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use crate::models::NewDeletedCrate;
use crate::tests::builders::PublishBuilder;
use crate::tests::util::{RequestHelper, TestApp};
use chrono::{Duration, Utc};
use crates_io_database::schema::deleted_crates;
use diesel_async::RunQueryDsl;
use googletest::prelude::*;
use http::StatusCode;
use insta::assert_snapshot;

#[tokio::test(flavor = "multi_thread")]
async fn test_recently_deleted_crate_with_same_name() -> anyhow::Result<()> {
let (app, _, _, token) = TestApp::full().with_token();
let mut conn = app.async_db_conn().await;

let now = Utc::now();
let created_at = now - Duration::hours(24);
let deleted_at = now - Duration::hours(1);
let available_at = "2099-12-25T12:34:56Z".parse()?;

let deleted_crate = NewDeletedCrate::builder("actix_web")
.created_at(&created_at)
.deleted_at(&deleted_at)
.available_at(&available_at)
.build();

diesel::insert_into(deleted_crates::table)
.values(deleted_crate)
.execute(&mut conn)
.await?;

let crate_to_publish = PublishBuilder::new("actix-web", "1.0.0");
let response = token.publish_crate(crate_to_publish).await;
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"A crate with the name `actix_web` was recently deleted. Reuse of this name will be available after 2099-12-25T12:34:56Z."}]}"#);
assert_that!(app.stored_files().await, empty());

Ok(())
}
1 change: 1 addition & 0 deletions src/tests/krate/publish/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod auth;
mod basics;
mod build_metadata;
mod categories;
mod deleted_crates;
mod dependencies;
mod emails;
mod features;
Expand Down

0 comments on commit a11076b

Please sign in to comment.