Skip to content

Commit aeb1356

Browse files
committed
rate limit yanking and unyanking
1 parent 4358690 commit aeb1356

File tree

3 files changed

+52
-0
lines changed

3 files changed

+52
-0
lines changed

src/controllers/version/yank.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::controllers::cargo_prelude::*;
88
use crate::models::token::EndpointScope;
99
use crate::models::Rights;
1010
use crate::models::{insert_version_owner_action, VersionAction};
11+
use crate::rate_limiter::LimitedAction;
1112
use crate::schema::versions;
1213

1314
/// Handles the `DELETE /crates/:crate_id/:version/yank` route.
@@ -58,6 +59,10 @@ fn modify_yank(
5859
.for_crate(crate_name)
5960
.check(req, conn)?;
6061

62+
state
63+
.rate_limiter
64+
.check_rate_limit(auth.user_id(), LimitedAction::YankUnyank, conn)?;
65+
6166
let (version, krate) = version_and_crate(conn, crate_name, version)?;
6267
let api_token_id = auth.api_token_id();
6368
let user = auth.user();

src/rate_limiter.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pg_enum! {
1313
pub enum LimitedAction {
1414
PublishNew = 0,
1515
PublishUpdate = 1,
16+
YankUnyank = 2,
1617
}
1718
}
1819

@@ -21,20 +22,23 @@ impl LimitedAction {
2122
match self {
2223
LimitedAction::PublishNew => 60 * 60,
2324
LimitedAction::PublishUpdate => 60,
25+
LimitedAction::YankUnyank => 60,
2426
}
2527
}
2628

2729
pub fn default_burst(&self) -> i32 {
2830
match self {
2931
LimitedAction::PublishNew => 5,
3032
LimitedAction::PublishUpdate => 30,
33+
LimitedAction::YankUnyank => 30,
3134
}
3235
}
3336

3437
pub fn env_var_key(&self) -> &'static str {
3538
match self {
3639
LimitedAction::PublishNew => "PUBLISH_NEW",
3740
LimitedAction::PublishUpdate => "PUBLISH_EXISTING",
41+
LimitedAction::YankUnyank => "YANK_UNYANK",
3842
}
3943
}
4044

@@ -46,6 +50,9 @@ impl LimitedAction {
4650
LimitedAction::PublishUpdate => {
4751
"You have published too many updates to existing crates in a short period of time"
4852
}
53+
LimitedAction::YankUnyank => {
54+
"You have yanked or unyanked too many versions in a short period of time"
55+
}
4956
}
5057
}
5158
}

src/tests/krate/yanking.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::builders::PublishBuilder;
22
use crate::routes::crates::versions::yank_unyank::YankRequestHelper;
33
use crate::util::{RequestHelper, TestApp};
4+
use crates_io::rate_limiter::LimitedAction;
5+
use std::time::Duration;
46

57
#[test]
68
#[allow(unknown_lints, clippy::bool_assert_comparison)] // for claim::assert_some_eq! with bool
@@ -60,6 +62,44 @@ fn yank_works_as_intended() {
6062
assert!(!json.version.yanked);
6163
}
6264

65+
#[test]
66+
fn test_yank_rate_limiting() {
67+
#[track_caller]
68+
fn check_yanked(app: &TestApp, is_yanked: bool) {
69+
let crates = app.crates_from_index_head("yankable");
70+
assert_eq!(crates.len(), 1);
71+
assert_some_eq!(crates[0].yanked, is_yanked);
72+
}
73+
74+
let (app, _, _, token) = TestApp::full()
75+
.with_rate_limit(LimitedAction::YankUnyank, Duration::from_millis(500), 1)
76+
.with_token();
77+
78+
// Upload a new crate
79+
let crate_to_publish = PublishBuilder::new("yankable");
80+
token.publish_crate(crate_to_publish).good();
81+
check_yanked(&app, false);
82+
83+
// Yank the crate
84+
token.yank("yankable", "1.0.0").good();
85+
check_yanked(&app, true);
86+
87+
// Try unyanking the crate, will get rate limited
88+
token.unyank("yankable", "1.0.0").assert_rate_limited();
89+
check_yanked(&app, true);
90+
91+
// Let the rate limit refill.
92+
std::thread::sleep(Duration::from_millis(500));
93+
94+
// Unyanking now works.
95+
token.unyank("yankable", "1.0.0").good();
96+
check_yanked(&app, false);
97+
98+
// Yanking again will trigger the rate limit.
99+
token.yank("yankable", "1.0.0").assert_rate_limited();
100+
check_yanked(&app, false);
101+
}
102+
63103
#[test]
64104
fn yank_max_version() {
65105
let (_, anon, _, token) = TestApp::full().with_token();

0 commit comments

Comments
 (0)