summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorByron Jones <glob@mozilla.com>2014-10-31 08:20:42 +0100
committerByron Jones <glob@mozilla.com>2014-10-31 08:20:42 +0100
commit908480c98aa48a9d1caf09ee00f3cfe0863afec2 (patch)
treea15dc741c3fe821865291a0789fda1607d2a67a4 /Bugzilla
parentf1fda7c8b9cf4646374cc708c14942e5feed82d1 (diff)
downloadbugzilla-908480c98aa48a9d1caf09ee00f3cfe0863afec2.tar.gz
bugzilla-908480c98aa48a9d1caf09ee00f3cfe0863afec2.tar.xz
Bug 1062739: add the ability for administrators to limit the number of emails sent to a user per minute and hour
r=dylan,a=glob
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Constants.pm18
-rw-r--r--Bugzilla/DB/Schema.pm12
-rw-r--r--Bugzilla/Install/Requirements.pm4
-rw-r--r--Bugzilla/Job/BugMail.pm16
-rw-r--r--Bugzilla/Job/Mailer.pm20
-rw-r--r--Bugzilla/Mailer.pm53
6 files changed, 101 insertions, 22 deletions
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index 4c1f11003..a770e7eb7 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -195,6 +195,12 @@ use Memoize;
MOST_FREQUENT_THRESHOLD
MARKDOWN_TAB_WIDTH
+
+ EMAIL_LIMIT_PER_MINUTE
+ EMAIL_LIMIT_PER_HOUR
+ EMAIL_LIMIT_EXCEPTION
+
+ JOB_QUEUE_VIEW_MAX_JOBS
);
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
@@ -641,6 +647,18 @@ use constant MOST_FREQUENT_THRESHOLD => 2;
# by Markdown engine
use constant MARKDOWN_TAB_WIDTH => 2;
+# The maximum number of emails per minute and hour a recipient can receive.
+# Email will be queued/backlogged to avoid exceeeding these limits.
+# Setting a limit to 0 will disable this feature.
+use constant EMAIL_LIMIT_PER_MINUTE => 1000;
+use constant EMAIL_LIMIT_PER_HOUR => 2500;
+# Don't change this exception message.
+use constant EMAIL_LIMIT_EXCEPTION => "email_limit_exceeded\n";
+
+# The maximum number of jobs to show when viewing the job queue
+# (view_job_queue.cgi).
+use constant JOB_QUEUE_VIEW_MAX_JOBS => 500;
+
sub bz_locations {
# Force memoize() to re-compute data per project, to avoid
# sharing the same data across different installations.
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index ebe2cb426..0698585bb 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -1640,6 +1640,18 @@ use constant ABSTRACT_SCHEMA => {
],
},
+ email_rates => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1,
+ PRIMARYKEY => 1},
+ recipient => {TYPE => 'varchar(255)', NOTNULL => 1},
+ message_ts => {TYPE => 'DATETIME', NOTNULL => 1},
+ ],
+ INDEXES => [
+ email_rates_idx => [qw(recipient message_ts)],
+ ],
+ },
+
# THESCHWARTZ TABLES
# ------------------
# Note: In the standard TheSchwartz schema, most integers are unsigned,
diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm
index db3d7b028..491bf8a72 100644
--- a/Bugzilla/Install/Requirements.pm
+++ b/Bugzilla/Install/Requirements.pm
@@ -349,8 +349,8 @@ sub OPTIONAL_MODULES {
{
package => 'TheSchwartz',
module => 'TheSchwartz',
- # 1.07 supports the prioritization of jobs.
- version => 1.07,
+ # 1.10 supports declining of jobs.
+ version => 1.10,
feature => ['jobqueue'],
},
{
diff --git a/Bugzilla/Job/BugMail.pm b/Bugzilla/Job/BugMail.pm
index e0b7f5448..b4887c470 100644
--- a/Bugzilla/Job/BugMail.pm
+++ b/Bugzilla/Job/BugMail.pm
@@ -14,19 +14,9 @@ use warnings;
use Bugzilla::BugMail;
BEGIN { eval "use parent qw(Bugzilla::Job::Mailer)"; }
-sub work {
- my ($class, $job) = @_;
- my $success = eval {
- Bugzilla::BugMail::dequeue($job->arg->{vars});
- 1;
- };
- if (!$success) {
- $job->failed($@);
- undef $@;
- }
- else {
- $job->completed;
- }
+sub process_job {
+ my ($class, $arg) = @_;
+ Bugzilla::BugMail::dequeue($arg->{vars});
}
1;
diff --git a/Bugzilla/Job/Mailer.pm b/Bugzilla/Job/Mailer.pm
index cd1c23445..7e7549de8 100644
--- a/Bugzilla/Job/Mailer.pm
+++ b/Bugzilla/Job/Mailer.pm
@@ -11,6 +11,7 @@ use 5.10.1;
use strict;
use warnings;
+use Bugzilla::Constants;
use Bugzilla::Mailer;
BEGIN { eval "use parent qw(TheSchwartz::Worker)"; }
@@ -32,15 +33,24 @@ sub retry_delay {
sub work {
my ($class, $job) = @_;
- my $msg = $job->arg->{msg};
- my $success = eval { MessageToMTA($msg, 1); 1; };
- if (!$success) {
- $job->failed($@);
+ eval { $class->process_job($job->arg) };
+ if (my $error = $@) {
+ if ($error eq EMAIL_LIMIT_EXCEPTION) {
+ $job->declined();
+ }
+ else {
+ $job->failed($error);
+ }
undef $@;
- }
+ }
else {
$job->completed;
}
}
+sub process_job {
+ my ($class, $arg) = @_;
+ MessageToMTA($arg, 1);
+}
+
1;
diff --git a/Bugzilla/Mailer.pm b/Bugzilla/Mailer.pm
index 4447d4046..389b6f69e 100644
--- a/Bugzilla/Mailer.pm
+++ b/Bugzilla/Mailer.pm
@@ -41,6 +41,8 @@ sub MessageToMTA {
return;
}
+ my $dbh = Bugzilla->dbh;
+
my $email;
if (ref $msg) {
$email = $msg;
@@ -58,14 +60,50 @@ sub MessageToMTA {
# email immediately, in case the transaction is rolled back. Instead we
# insert it into the mail_staging table, and bz_commit_transaction calls
# send_staged_mail() after the transaction is committed.
- if (! $send_now && Bugzilla->dbh->bz_in_transaction()) {
+ if (! $send_now && $dbh->bz_in_transaction()) {
# The e-mail string may contain tainted values.
my $string = $email->as_string;
trick_taint($string);
- Bugzilla->dbh->do("INSERT INTO mail_staging (message) VALUES(?)", undef, $string);
+ $dbh->do("INSERT INTO mail_staging (message) VALUES(?)", undef, $string);
return;
}
+ # Ensure that we are not sending emails too quickly to recipients.
+ if (Bugzilla->params->{use_mailer_queue}
+ && (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR))
+ {
+ $dbh->do(
+ "DELETE FROM email_rates WHERE message_ts < "
+ . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '1', 'HOUR'));
+
+ my $recipient = $email->header('To');
+
+ if (EMAIL_LIMIT_PER_MINUTE) {
+ my $minute_rate = $dbh->selectrow_array(
+ "SELECT COUNT(*)
+ FROM email_rates
+ WHERE recipient = ? AND message_ts >= "
+ . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '1', 'MINUTE'),
+ undef,
+ $recipient);
+ if ($minute_rate >= EMAIL_LIMIT_PER_MINUTE) {
+ die EMAIL_LIMIT_EXCEPTION;
+ }
+ }
+ if (EMAIL_LIMIT_PER_HOUR) {
+ my $hour_rate = $dbh->selectrow_array(
+ "SELECT COUNT(*)
+ FROM email_rates
+ WHERE recipient = ? AND message_ts >= "
+ . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', '1', 'HOUR'),
+ undef,
+ $recipient);
+ if ($hour_rate >= EMAIL_LIMIT_PER_HOUR) {
+ die EMAIL_LIMIT_EXCEPTION;
+ }
+ }
+ }
+
# We add this header to uniquely identify all email that we
# send as coming from this Bugzilla installation.
#
@@ -181,6 +219,17 @@ sub MessageToMTA {
ThrowCodeError('mail_send_error', { msg => $@->message, mail => $email });
}
}
+
+ # insert into email_rates
+ if (Bugzilla->params->{use_mailer_queue}
+ && (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR))
+ {
+ $dbh->do(
+ "INSERT INTO email_rates(recipient, message_ts) VALUES (?, LOCALTIMESTAMP(0))",
+ undef,
+ $email->header('To')
+ );
+ }
}
# Builds header suitable for use as a threading marker in email notifications