Skip to content

Commit c03f327

Browse files
committed
Simplify daily version limit implementation
The `PublishRateLimit` struct is somewhat closely tied to the current system of limiting new crate creation. Instead of adding the new daily versions limit to it, we can just keep the code in the controller without having to touch any of the `PublishRateLimit` constructor calls.
1 parent fc6048c commit c03f327

File tree

6 files changed

+30
-50
lines changed

6 files changed

+30
-50
lines changed

src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub struct Server {
2525
pub max_upload_size: u64,
2626
pub max_unpack_size: u64,
2727
pub publish_rate_limit: PublishRateLimit,
28+
pub new_version_rate_limit: Option<u32>,
2829
pub blocked_traffic: Vec<(String, Vec<String>)>,
2930
pub max_allowed_page_offset: u32,
3031
pub page_offset_ua_blocklist: Vec<String>,
@@ -118,6 +119,7 @@ impl Default for Server {
118119
max_upload_size: 10 * 1024 * 1024, // 10 MB default file upload size limit
119120
max_unpack_size: 512 * 1024 * 1024, // 512 MB max when decompressed
120121
publish_rate_limit: Default::default(),
122+
new_version_rate_limit: env_optional("MAX_NEW_VERSIONS_DAILY"),
121123
blocked_traffic: blocked_traffic(),
122124
max_allowed_page_offset: env_optional("WEB_MAX_ALLOWED_PAGE_OFFSET").unwrap_or(200),
123125
page_offset_ua_blocklist,

src/controllers/krate/publish.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,14 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
128128
)));
129129
}
130130

131-
app.config
132-
.publish_rate_limit
133-
.check_daily_limit(krate.id, &conn)?;
131+
if let Some(daily_version_limit) = app.config.new_version_rate_limit {
132+
let published_today = count_versions_published_today(krate.id, &conn)?;
133+
if published_today >= daily_version_limit as i64 {
134+
return Err(cargo_err(
135+
"You have published too many versions of this crate in the last 24 hours",
136+
));
137+
}
138+
}
134139

135140
// Length of the .crate tarball, which appears after the metadata in the request body.
136141
// TODO: Not sure why we're using the total content length (metadata + .crate file length)
@@ -263,6 +268,19 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
263268
})
264269
}
265270

271+
/// Counts the number of versions for `krate_id` that were published within
272+
/// the last 24 hours.
273+
fn count_versions_published_today(krate_id: i32, conn: &PgConnection) -> QueryResult<i64> {
274+
use crate::schema::versions::dsl::*;
275+
use diesel::dsl::{now, IntervalDsl};
276+
277+
versions
278+
.filter(crate_id.eq(krate_id))
279+
.filter(created_at.gt(now - 24.hours()))
280+
.count()
281+
.get_result(conn)
282+
}
283+
266284
/// Used by the `krate::new` function.
267285
///
268286
/// This function parses the JSON headers to interpret the data and validates

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub mod email;
4646
pub mod github;
4747
pub mod metrics;
4848
pub mod middleware;
49-
pub mod publish_rate_limit;
49+
mod publish_rate_limit;
5050
pub mod schema;
5151
pub mod sql;
5252
mod test_util;

src/publish_rate_limit.rs

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ use std::time::Duration;
66

77
use crate::schema::{publish_limit_buckets, publish_rate_overrides};
88
use crate::sql::{date_part, floor, greatest, interval_part, least};
9-
use crate::util::errors::{cargo_err, AppResult, TooManyRequests};
9+
use crate::util::errors::{AppResult, TooManyRequests};
1010

1111
#[derive(Debug, Clone, Copy)]
1212
pub struct PublishRateLimit {
1313
pub rate: Duration,
1414
pub burst: i32,
15-
pub daily: Option<i64>,
1615
}
1716

1817
impl Default for PublishRateLimit {
@@ -27,14 +26,9 @@ impl Default for PublishRateLimit {
2726
.parse()
2827
.ok()
2928
.unwrap_or(5);
30-
let daily = dotenv::var("MAX_NEW_VERSIONS_DAILY")
31-
.unwrap_or_default()
32-
.parse()
33-
.ok();
3429
Self {
3530
rate: Duration::from_secs(60) * minutes,
3631
burst,
37-
daily,
3832
}
3933
}
4034
}
@@ -112,27 +106,6 @@ impl PublishRateLimit {
112106
use diesel::dsl::*;
113107
(self.rate.as_millis() as i64).milliseconds()
114108
}
115-
116-
pub fn check_daily_limit(&self, krate_id: i32, conn: &PgConnection) -> AppResult<()> {
117-
use crate::schema::versions::dsl::*;
118-
use diesel::dsl::{count_star, now, IntervalDsl};
119-
120-
if let Some(daily_limit) = self.daily {
121-
let today: i64 = versions
122-
.filter(crate_id.eq(krate_id))
123-
.filter(created_at.gt(now - 24.hours()))
124-
.select(count_star())
125-
.first(conn)
126-
.optional()?
127-
.unwrap_or_default();
128-
if today >= daily_limit {
129-
return Err(cargo_err(
130-
"You have published too many versions of this crate in the last 24 hours",
131-
));
132-
}
133-
}
134-
Ok(())
135-
}
136109
}
137110

138111
#[cfg(test)]
@@ -149,7 +122,6 @@ mod tests {
149122
let rate = PublishRateLimit {
150123
rate: Duration::from_secs(1),
151124
burst: 10,
152-
daily: None,
153125
};
154126
let bucket = rate.take_token(new_user(&conn, "user1")?, now, &conn)?;
155127
let expected = Bucket {
@@ -162,7 +134,6 @@ mod tests {
162134
let rate = PublishRateLimit {
163135
rate: Duration::from_millis(50),
164136
burst: 20,
165-
daily: None,
166137
};
167138
let bucket = rate.take_token(new_user(&conn, "user2")?, now, &conn)?;
168139
let expected = Bucket {
@@ -182,7 +153,6 @@ mod tests {
182153
let rate = PublishRateLimit {
183154
rate: Duration::from_secs(1),
184155
burst: 10,
185-
daily: None,
186156
};
187157
let user_id = new_user_bucket(&conn, 5, now)?.user_id;
188158
let bucket = rate.take_token(user_id, now, &conn)?;
@@ -203,7 +173,6 @@ mod tests {
203173
let rate = PublishRateLimit {
204174
rate: Duration::from_secs(1),
205175
burst: 10,
206-
daily: None,
207176
};
208177
let user_id = new_user_bucket(&conn, 5, now)?.user_id;
209178
let refill_time = now + chrono::Duration::seconds(2);
@@ -229,7 +198,6 @@ mod tests {
229198
let rate = PublishRateLimit {
230199
rate: Duration::from_millis(100),
231200
burst: 10,
232-
daily: None,
233201
};
234202
let user_id = new_user_bucket(&conn, 5, now)?.user_id;
235203
let refill_time = now + chrono::Duration::milliseconds(300);
@@ -251,7 +219,6 @@ mod tests {
251219
let rate = PublishRateLimit {
252220
rate: Duration::from_millis(100),
253221
burst: 10,
254-
daily: None,
255222
};
256223
let user_id = new_user_bucket(&conn, 5, now)?.user_id;
257224
let bucket = rate.take_token(user_id, now + chrono::Duration::milliseconds(250), &conn)?;
@@ -273,7 +240,6 @@ mod tests {
273240
let rate = PublishRateLimit {
274241
rate: Duration::from_secs(1),
275242
burst: 10,
276-
daily: None,
277243
};
278244
let user_id = new_user_bucket(&conn, 1, now)?.user_id;
279245
let bucket = rate.take_token(user_id, now, &conn)?;
@@ -297,7 +263,6 @@ mod tests {
297263
let rate = PublishRateLimit {
298264
rate: Duration::from_secs(1),
299265
burst: 10,
300-
daily: None,
301266
};
302267
let user_id = new_user_bucket(&conn, 0, now)?.user_id;
303268
let refill_time = now + chrono::Duration::seconds(1);
@@ -320,7 +285,6 @@ mod tests {
320285
let rate = PublishRateLimit {
321286
rate: Duration::from_secs(1),
322287
burst: 10,
323-
daily: None,
324288
};
325289
let user_id = new_user_bucket(&conn, 8, now)?.user_id;
326290
let refill_time = now + chrono::Duration::seconds(4);
@@ -343,7 +307,6 @@ mod tests {
343307
let rate = PublishRateLimit {
344308
rate: Duration::from_secs(1),
345309
burst: 10,
346-
daily: None,
347310
};
348311
let user_id = new_user(&conn, "user1")?;
349312
let other_user_id = new_user(&conn, "user2")?;
@@ -371,7 +334,6 @@ mod tests {
371334
let rate = PublishRateLimit {
372335
rate: Duration::from_secs(1),
373336
burst: 10,
374-
daily: None,
375337
};
376338
let user_id = new_user(&conn, "user1")?;
377339
let other_user_id = new_user(&conn, "user2")?;

src/tests/util/test_app.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use super::{MockAnonymousUser, MockCookieUser, MockTokenUser};
22
use crate::record;
33
use crate::util::{chaosproxy::ChaosProxy, fresh_schema::FreshSchema};
44
use cargo_registry::config::{self, DbPoolConfig};
5-
use cargo_registry::publish_rate_limit::PublishRateLimit;
65
use cargo_registry::{background_jobs::Environment, db::DieselPool, App, Emails};
76
use cargo_registry_index::testing::UpstreamIndex;
87
use cargo_registry_index::{Credentials, Repository as WorkerRepository, RepositoryConfig};
@@ -341,10 +340,8 @@ fn simple_config() -> config::Server {
341340
gh_base_url: "http://api.github.com".to_string(),
342341
max_upload_size: 3000,
343342
max_unpack_size: 2000,
344-
publish_rate_limit: PublishRateLimit {
345-
daily: Some(10),
346-
..Default::default()
347-
},
343+
publish_rate_limit: Default::default(),
344+
new_version_rate_limit: Some(10),
348345
blocked_traffic: Default::default(),
349346
max_allowed_page_offset: 200,
350347
page_offset_ua_blocklist: vec![],

src/tests/version.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,10 @@ fn version_size() {
180180

181181
#[test]
182182
fn daily_limit() {
183-
let (_, _, user) = TestApp::full().with_user();
183+
let (app, _, user) = TestApp::full().with_user();
184184

185-
for version in 1..=10 {
185+
let max_daily_versions = app.as_inner().config.new_version_rate_limit.unwrap();
186+
for version in 1..=max_daily_versions {
186187
let crate_to_publish =
187188
PublishBuilder::new("foo_daily_limit").version(&format!("0.0.{}", version));
188189
user.publish_crate(crate_to_publish).good();

0 commit comments

Comments
 (0)