From fc6048cd15dc8f1bd8a9a7026b8b8f584e652e68 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Fri, 7 Oct 2022 21:04:55 +0200 Subject: [PATCH 1/2] Introduce the concept of daily limit --- src/controllers/krate/publish.rs | 4 + src/lib.rs | 2 +- src/publish_rate_limit.rs | 40 +- src/tests/http-data/version_daily_limit | 602 ++++++++++++++++++++++++ src/tests/util/test_app.rs | 6 +- src/tests/version.rs | 20 + 6 files changed, 671 insertions(+), 3 deletions(-) create mode 100644 src/tests/http-data/version_daily_limit diff --git a/src/controllers/krate/publish.rs b/src/controllers/krate/publish.rs index 81b444c968d..8c89c074641 100644 --- a/src/controllers/krate/publish.rs +++ b/src/controllers/krate/publish.rs @@ -128,6 +128,10 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult { ))); } + app.config + .publish_rate_limit + .check_daily_limit(krate.id, &conn)?; + // Length of the .crate tarball, which appears after the metadata in the request body. // TODO: Not sure why we're using the total content length (metadata + .crate file length) // to compare against the max upload size... investigate that and perhaps change to use diff --git a/src/lib.rs b/src/lib.rs index 7912ed4b221..825c2aa5790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ pub mod email; pub mod github; pub mod metrics; pub mod middleware; -mod publish_rate_limit; +pub mod publish_rate_limit; pub mod schema; pub mod sql; mod test_util; diff --git a/src/publish_rate_limit.rs b/src/publish_rate_limit.rs index f6d03408663..71fb7732f69 100644 --- a/src/publish_rate_limit.rs +++ b/src/publish_rate_limit.rs @@ -6,12 +6,13 @@ use std::time::Duration; use crate::schema::{publish_limit_buckets, publish_rate_overrides}; use crate::sql::{date_part, floor, greatest, interval_part, least}; -use crate::util::errors::{AppResult, TooManyRequests}; +use crate::util::errors::{cargo_err, AppResult, TooManyRequests}; #[derive(Debug, Clone, Copy)] pub struct PublishRateLimit { pub rate: Duration, pub burst: i32, + pub daily: Option, } impl Default for PublishRateLimit { @@ -26,9 +27,14 @@ impl Default for PublishRateLimit { .parse() .ok() .unwrap_or(5); + let daily = dotenv::var("MAX_NEW_VERSIONS_DAILY") + .unwrap_or_default() + .parse() + .ok(); Self { rate: Duration::from_secs(60) * minutes, burst, + daily, } } } @@ -106,6 +112,27 @@ impl PublishRateLimit { use diesel::dsl::*; (self.rate.as_millis() as i64).milliseconds() } + + pub fn check_daily_limit(&self, krate_id: i32, conn: &PgConnection) -> AppResult<()> { + use crate::schema::versions::dsl::*; + use diesel::dsl::{count_star, now, IntervalDsl}; + + if let Some(daily_limit) = self.daily { + let today: i64 = versions + .filter(crate_id.eq(krate_id)) + .filter(created_at.gt(now - 24.hours())) + .select(count_star()) + .first(conn) + .optional()? + .unwrap_or_default(); + if today >= daily_limit { + return Err(cargo_err( + "You have published too many versions of this crate in the last 24 hours", + )); + } + } + Ok(()) + } } #[cfg(test)] @@ -122,6 +149,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, + daily: None, }; let bucket = rate.take_token(new_user(&conn, "user1")?, now, &conn)?; let expected = Bucket { @@ -134,6 +162,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_millis(50), burst: 20, + daily: None, }; let bucket = rate.take_token(new_user(&conn, "user2")?, now, &conn)?; let expected = Bucket { @@ -153,6 +182,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, + daily: None, }; let user_id = new_user_bucket(&conn, 5, now)?.user_id; let bucket = rate.take_token(user_id, now, &conn)?; @@ -173,6 +203,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, + daily: None, }; let user_id = new_user_bucket(&conn, 5, now)?.user_id; let refill_time = now + chrono::Duration::seconds(2); @@ -198,6 +229,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_millis(100), burst: 10, + daily: None, }; let user_id = new_user_bucket(&conn, 5, now)?.user_id; let refill_time = now + chrono::Duration::milliseconds(300); @@ -219,6 +251,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_millis(100), burst: 10, + daily: None, }; let user_id = new_user_bucket(&conn, 5, now)?.user_id; let bucket = rate.take_token(user_id, now + chrono::Duration::milliseconds(250), &conn)?; @@ -240,6 +273,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, + daily: None, }; let user_id = new_user_bucket(&conn, 1, now)?.user_id; let bucket = rate.take_token(user_id, now, &conn)?; @@ -263,6 +297,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, + daily: None, }; let user_id = new_user_bucket(&conn, 0, now)?.user_id; let refill_time = now + chrono::Duration::seconds(1); @@ -285,6 +320,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, + daily: None, }; let user_id = new_user_bucket(&conn, 8, now)?.user_id; let refill_time = now + chrono::Duration::seconds(4); @@ -307,6 +343,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, + daily: None, }; let user_id = new_user(&conn, "user1")?; let other_user_id = new_user(&conn, "user2")?; @@ -334,6 +371,7 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, + daily: None, }; let user_id = new_user(&conn, "user1")?; let other_user_id = new_user(&conn, "user2")?; diff --git a/src/tests/http-data/version_daily_limit b/src/tests/http-data/version_daily_limit new file mode 100644 index 00000000000..4eb24cc67f7 --- /dev/null +++ b/src/tests/http-data/version_daily_limit @@ -0,0 +1,602 @@ +[ + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.1.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "169" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9Cg==" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.2.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "338" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuMiIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQo=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.3.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "507" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuMiIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjMiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0K" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.4.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "676" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuMiIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjMiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC40IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9Cg==" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.5.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "845" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuMiIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjMiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC40IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuNSIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQo=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.6.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "1014" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuMiIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjMiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC40IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuNSIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjYiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0K" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.7.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "1183" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuMiIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjMiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC40IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuNSIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjYiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC43IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9Cg==" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.8.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "1352" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuMiIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjMiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC40IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuNSIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjYiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC43IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuOCIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQo=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.9.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "1521" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuMiIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjMiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC40IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuNSIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjYiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC43IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuOCIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjkiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0K" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo_daily_limit/foo_daily_limit-0.0.10.crate", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "35" + ], + [ + "content-type", + "application/gzip" + ] + ], + "body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + }, + { + "request": { + "uri": "http://alexcrichton-test.s3.amazonaws.com/fo/o_/foo_daily_limit", + "method": "PUT", + "headers": [ + [ + "accept", + "*/*" + ], + [ + "accept-encoding", + "gzip" + ], + [ + "content-length", + "1691" + ], + [ + "content-type", + "text/plain" + ] + ], + "body": "eyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xIiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuMiIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjMiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC40IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuNSIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjYiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC43IiwiZGVwcyI6W10sImNrc3VtIjoiYWNiNTYwNGIxMjZhYzg5NGMxZWIxMWM0NTc1YmYyMDcyZmVhNjEyMzJhODg4ZTQ1Mzc3MGM3OWQ3ZWQ1NjQxOSIsImZlYXR1cmVzIjp7fSwieWFua2VkIjpmYWxzZSwibGlua3MiOm51bGx9CnsibmFtZSI6ImZvb19kYWlseV9saW1pdCIsInZlcnMiOiIwLjAuOCIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQp7Im5hbWUiOiJmb29fZGFpbHlfbGltaXQiLCJ2ZXJzIjoiMC4wLjkiLCJkZXBzIjpbXSwiY2tzdW0iOiJhY2I1NjA0YjEyNmFjODk0YzFlYjExYzQ1NzViZjIwNzJmZWE2MTIzMmE4ODhlNDUzNzcwYzc5ZDdlZDU2NDE5IiwiZmVhdHVyZXMiOnt9LCJ5YW5rZWQiOmZhbHNlLCJsaW5rcyI6bnVsbH0KeyJuYW1lIjoiZm9vX2RhaWx5X2xpbWl0IiwidmVycyI6IjAuMC4xMCIsImRlcHMiOltdLCJja3N1bSI6ImFjYjU2MDRiMTI2YWM4OTRjMWViMTFjNDU3NWJmMjA3MmZlYTYxMjMyYTg4OGU0NTM3NzBjNzlkN2VkNTY0MTkiLCJmZWF0dXJlcyI6e30sInlhbmtlZCI6ZmFsc2UsImxpbmtzIjpudWxsfQo=" + }, + "response": { + "status": 200, + "headers": [], + "body": "" + } + } +] diff --git a/src/tests/util/test_app.rs b/src/tests/util/test_app.rs index 086b06a33c7..75320888617 100644 --- a/src/tests/util/test_app.rs +++ b/src/tests/util/test_app.rs @@ -2,6 +2,7 @@ use super::{MockAnonymousUser, MockCookieUser, MockTokenUser}; use crate::record; use crate::util::{chaosproxy::ChaosProxy, fresh_schema::FreshSchema}; use cargo_registry::config::{self, DbPoolConfig}; +use cargo_registry::publish_rate_limit::PublishRateLimit; use cargo_registry::{background_jobs::Environment, db::DieselPool, App, Emails}; use cargo_registry_index::testing::UpstreamIndex; use cargo_registry_index::{Credentials, Repository as WorkerRepository, RepositoryConfig}; @@ -340,7 +341,10 @@ fn simple_config() -> config::Server { gh_base_url: "http://api.github.com".to_string(), max_upload_size: 3000, max_unpack_size: 2000, - publish_rate_limit: Default::default(), + publish_rate_limit: PublishRateLimit { + daily: Some(10), + ..Default::default() + }, blocked_traffic: Default::default(), max_allowed_page_offset: 200, page_offset_ua_blocklist: vec![], diff --git a/src/tests/version.rs b/src/tests/version.rs index 79b6bec2734..15ad756ce66 100644 --- a/src/tests/version.rs +++ b/src/tests/version.rs @@ -177,3 +177,23 @@ fn version_size() { .expect("Could not find v2.0.0"); assert_eq!(version2.crate_size, Some(91)); } + +#[test] +fn daily_limit() { + let (_, _, user) = TestApp::full().with_user(); + + for version in 1..=10 { + let crate_to_publish = + PublishBuilder::new("foo_daily_limit").version(&format!("0.0.{}", version)); + user.publish_crate(crate_to_publish).good(); + } + + let crate_to_publish = PublishBuilder::new("foo_daily_limit").version("1.0.0"); + let response = user.publish_crate(crate_to_publish); + assert!(response.status().is_success()); + let json = response.into_json(); + assert_eq!( + json["errors"][0]["detail"], + "You have published too many versions of this crate in the last 24 hours" + ); +} From c03f327bf8bef142a0b0cb6e1a415b69239016e9 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sun, 23 Oct 2022 11:53:44 +0200 Subject: [PATCH 2/2] 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. --- src/config.rs | 2 ++ src/controllers/krate/publish.rs | 24 ++++++++++++++++--- src/lib.rs | 2 +- src/publish_rate_limit.rs | 40 +------------------------------- src/tests/util/test_app.rs | 7 ++---- src/tests/version.rs | 5 ++-- 6 files changed, 30 insertions(+), 50 deletions(-) diff --git a/src/config.rs b/src/config.rs index 6f193806763..44368965007 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,6 +25,7 @@ pub struct Server { pub max_upload_size: u64, pub max_unpack_size: u64, pub publish_rate_limit: PublishRateLimit, + pub new_version_rate_limit: Option, pub blocked_traffic: Vec<(String, Vec)>, pub max_allowed_page_offset: u32, pub page_offset_ua_blocklist: Vec, @@ -118,6 +119,7 @@ impl Default for Server { max_upload_size: 10 * 1024 * 1024, // 10 MB default file upload size limit max_unpack_size: 512 * 1024 * 1024, // 512 MB max when decompressed publish_rate_limit: Default::default(), + new_version_rate_limit: env_optional("MAX_NEW_VERSIONS_DAILY"), blocked_traffic: blocked_traffic(), max_allowed_page_offset: env_optional("WEB_MAX_ALLOWED_PAGE_OFFSET").unwrap_or(200), page_offset_ua_blocklist, diff --git a/src/controllers/krate/publish.rs b/src/controllers/krate/publish.rs index 8c89c074641..9e90557db0a 100644 --- a/src/controllers/krate/publish.rs +++ b/src/controllers/krate/publish.rs @@ -128,9 +128,14 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult { ))); } - app.config - .publish_rate_limit - .check_daily_limit(krate.id, &conn)?; + if let Some(daily_version_limit) = app.config.new_version_rate_limit { + let published_today = count_versions_published_today(krate.id, &conn)?; + if published_today >= daily_version_limit as i64 { + return Err(cargo_err( + "You have published too many versions of this crate in the last 24 hours", + )); + } + } // Length of the .crate tarball, which appears after the metadata in the request body. // 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 { }) } +/// Counts the number of versions for `krate_id` that were published within +/// the last 24 hours. +fn count_versions_published_today(krate_id: i32, conn: &PgConnection) -> QueryResult { + use crate::schema::versions::dsl::*; + use diesel::dsl::{now, IntervalDsl}; + + versions + .filter(crate_id.eq(krate_id)) + .filter(created_at.gt(now - 24.hours())) + .count() + .get_result(conn) +} + /// Used by the `krate::new` function. /// /// This function parses the JSON headers to interpret the data and validates diff --git a/src/lib.rs b/src/lib.rs index 825c2aa5790..7912ed4b221 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ pub mod email; pub mod github; pub mod metrics; pub mod middleware; -pub mod publish_rate_limit; +mod publish_rate_limit; pub mod schema; pub mod sql; mod test_util; diff --git a/src/publish_rate_limit.rs b/src/publish_rate_limit.rs index 71fb7732f69..f6d03408663 100644 --- a/src/publish_rate_limit.rs +++ b/src/publish_rate_limit.rs @@ -6,13 +6,12 @@ use std::time::Duration; use crate::schema::{publish_limit_buckets, publish_rate_overrides}; use crate::sql::{date_part, floor, greatest, interval_part, least}; -use crate::util::errors::{cargo_err, AppResult, TooManyRequests}; +use crate::util::errors::{AppResult, TooManyRequests}; #[derive(Debug, Clone, Copy)] pub struct PublishRateLimit { pub rate: Duration, pub burst: i32, - pub daily: Option, } impl Default for PublishRateLimit { @@ -27,14 +26,9 @@ impl Default for PublishRateLimit { .parse() .ok() .unwrap_or(5); - let daily = dotenv::var("MAX_NEW_VERSIONS_DAILY") - .unwrap_or_default() - .parse() - .ok(); Self { rate: Duration::from_secs(60) * minutes, burst, - daily, } } } @@ -112,27 +106,6 @@ impl PublishRateLimit { use diesel::dsl::*; (self.rate.as_millis() as i64).milliseconds() } - - pub fn check_daily_limit(&self, krate_id: i32, conn: &PgConnection) -> AppResult<()> { - use crate::schema::versions::dsl::*; - use diesel::dsl::{count_star, now, IntervalDsl}; - - if let Some(daily_limit) = self.daily { - let today: i64 = versions - .filter(crate_id.eq(krate_id)) - .filter(created_at.gt(now - 24.hours())) - .select(count_star()) - .first(conn) - .optional()? - .unwrap_or_default(); - if today >= daily_limit { - return Err(cargo_err( - "You have published too many versions of this crate in the last 24 hours", - )); - } - } - Ok(()) - } } #[cfg(test)] @@ -149,7 +122,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, - daily: None, }; let bucket = rate.take_token(new_user(&conn, "user1")?, now, &conn)?; let expected = Bucket { @@ -162,7 +134,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_millis(50), burst: 20, - daily: None, }; let bucket = rate.take_token(new_user(&conn, "user2")?, now, &conn)?; let expected = Bucket { @@ -182,7 +153,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, - daily: None, }; let user_id = new_user_bucket(&conn, 5, now)?.user_id; let bucket = rate.take_token(user_id, now, &conn)?; @@ -203,7 +173,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, - daily: None, }; let user_id = new_user_bucket(&conn, 5, now)?.user_id; let refill_time = now + chrono::Duration::seconds(2); @@ -229,7 +198,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_millis(100), burst: 10, - daily: None, }; let user_id = new_user_bucket(&conn, 5, now)?.user_id; let refill_time = now + chrono::Duration::milliseconds(300); @@ -251,7 +219,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_millis(100), burst: 10, - daily: None, }; let user_id = new_user_bucket(&conn, 5, now)?.user_id; let bucket = rate.take_token(user_id, now + chrono::Duration::milliseconds(250), &conn)?; @@ -273,7 +240,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, - daily: None, }; let user_id = new_user_bucket(&conn, 1, now)?.user_id; let bucket = rate.take_token(user_id, now, &conn)?; @@ -297,7 +263,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, - daily: None, }; let user_id = new_user_bucket(&conn, 0, now)?.user_id; let refill_time = now + chrono::Duration::seconds(1); @@ -320,7 +285,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, - daily: None, }; let user_id = new_user_bucket(&conn, 8, now)?.user_id; let refill_time = now + chrono::Duration::seconds(4); @@ -343,7 +307,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, - daily: None, }; let user_id = new_user(&conn, "user1")?; let other_user_id = new_user(&conn, "user2")?; @@ -371,7 +334,6 @@ mod tests { let rate = PublishRateLimit { rate: Duration::from_secs(1), burst: 10, - daily: None, }; let user_id = new_user(&conn, "user1")?; let other_user_id = new_user(&conn, "user2")?; diff --git a/src/tests/util/test_app.rs b/src/tests/util/test_app.rs index 75320888617..d5603a2cdb3 100644 --- a/src/tests/util/test_app.rs +++ b/src/tests/util/test_app.rs @@ -2,7 +2,6 @@ use super::{MockAnonymousUser, MockCookieUser, MockTokenUser}; use crate::record; use crate::util::{chaosproxy::ChaosProxy, fresh_schema::FreshSchema}; use cargo_registry::config::{self, DbPoolConfig}; -use cargo_registry::publish_rate_limit::PublishRateLimit; use cargo_registry::{background_jobs::Environment, db::DieselPool, App, Emails}; use cargo_registry_index::testing::UpstreamIndex; use cargo_registry_index::{Credentials, Repository as WorkerRepository, RepositoryConfig}; @@ -341,10 +340,8 @@ fn simple_config() -> config::Server { gh_base_url: "http://api.github.com".to_string(), max_upload_size: 3000, max_unpack_size: 2000, - publish_rate_limit: PublishRateLimit { - daily: Some(10), - ..Default::default() - }, + publish_rate_limit: Default::default(), + new_version_rate_limit: Some(10), blocked_traffic: Default::default(), max_allowed_page_offset: 200, page_offset_ua_blocklist: vec![], diff --git a/src/tests/version.rs b/src/tests/version.rs index 15ad756ce66..1879184fe69 100644 --- a/src/tests/version.rs +++ b/src/tests/version.rs @@ -180,9 +180,10 @@ fn version_size() { #[test] fn daily_limit() { - let (_, _, user) = TestApp::full().with_user(); + let (app, _, user) = TestApp::full().with_user(); - for version in 1..=10 { + let max_daily_versions = app.as_inner().config.new_version_rate_limit.unwrap(); + for version in 1..=max_daily_versions { let crate_to_publish = PublishBuilder::new("foo_daily_limit").version(&format!("0.0.{}", version)); user.publish_crate(crate_to_publish).good();