Skip to content
Snippets Groups Projects
Commit 43fe701c authored by Alex Buijs's avatar Alex Buijs Committed by Rémy Coutable
Browse files

Add email to send to admin when a user is auto banned

For use with the feature
auto_ban_user_on_excessive_repo_downloads

Changelog: added
parent e1b04b33
No related branches found
No related tags found
No related merge requests found
Showing
with 159 additions and 76 deletions
Loading
@@ -15,5 +15,18 @@ def send_unsubscribed_notification(user_id)
Loading
@@ -15,5 +15,18 @@ def send_unsubscribed_notification(user_id)
email = user.notification_email_or_default email = user.notification_email_or_default
mail to: email, subject: "Unsubscribed from GitLab administrator notifications" mail to: email, subject: "Unsubscribed from GitLab administrator notifications"
end end
def user_auto_banned_email(admin_id, user_id, max_project_downloads:, within_seconds:)
admin = User.find(admin_id)
@user = User.find(user_id)
@max_project_downloads = max_project_downloads
@within_minutes = within_seconds / 60
Gitlab::I18n.with_locale(admin.preferred_language) do
email_with_layout(
to: admin.notification_email_or_default,
subject: subject(_("We've detected unusual activity")))
end
end
end end
end end
Loading
@@ -8,11 +8,9 @@ def autodevops_disabled_email(pipeline, recipient)
Loading
@@ -8,11 +8,9 @@ def autodevops_disabled_email(pipeline, recipient)
   
add_project_headers add_project_headers
   
mail(to: recipient, email_with_layout(
subject: auto_devops_disabled_subject(@project.name)) do |format| to: recipient,
format.html { render layout: 'mailer' } subject: auto_devops_disabled_subject(@project.name))
format.text { render layout: 'mailer' }
end
end end
   
private private
Loading
Loading
Loading
@@ -94,10 +94,9 @@ def import_issues_csv_email(user_id, project_id, results)
Loading
@@ -94,10 +94,9 @@ def import_issues_csv_email(user_id, project_id, results)
@project = Project.find(project_id) @project = Project.find(project_id)
@results = results @results = results
   
mail(to: @user.notification_email_for(@project.group), subject: subject('Imported issues')) do |format| email_with_layout(
format.html { render layout: 'mailer' } to: @user.notification_email_for(@project.group),
format.text { render layout: 'mailer' } subject: subject('Imported issues'))
end
end end
   
def issues_csv_email(user, project, csv_data, export_status) def issues_csv_email(user, project, csv_data, export_status)
Loading
@@ -110,10 +109,9 @@ def issues_csv_email(user, project, csv_data, export_status)
Loading
@@ -110,10 +109,9 @@ def issues_csv_email(user, project, csv_data, export_status)
   
filename = "#{project.full_path.parameterize}_issues_#{Date.today.iso8601}.csv" filename = "#{project.full_path.parameterize}_issues_#{Date.today.iso8601}.csv"
attachments[filename] = { content: csv_data, mime_type: 'text/csv' } attachments[filename] = { content: csv_data, mime_type: 'text/csv' }
mail(to: user.notification_email_for(@project.group), subject: subject("Exported issues")) do |format| email_with_layout(
format.html { render layout: 'mailer' } to: user.notification_email_for(@project.group),
format.text { render layout: 'mailer' } subject: subject("Exported issues"))
end
end end
   
private private
Loading
Loading
Loading
@@ -21,7 +21,7 @@ def member_access_requested_email(member_source_type, member_id, recipient_id)
Loading
@@ -21,7 +21,7 @@ def member_access_requested_email(member_source_type, member_id, recipient_id)
   
user = User.find(recipient_id) user = User.find(recipient_id)
   
member_email_with_layout( email_with_layout(
to: user.notification_email_for(notification_group), to: user.notification_email_for(notification_group),
subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}")) subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
end end
Loading
@@ -32,7 +32,7 @@ def member_access_granted_email(member_source_type, member_id)
Loading
@@ -32,7 +32,7 @@ def member_access_granted_email(member_source_type, member_id)
   
return unless member_exists? return unless member_exists?
   
member_email_with_layout( email_with_layout(
to: member.user.notification_email_for(notification_group), to: member.user.notification_email_for(notification_group),
subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was granted")) subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was granted"))
end end
Loading
@@ -47,7 +47,7 @@ def member_access_denied_email(member_source_type, source_id, user_id)
Loading
@@ -47,7 +47,7 @@ def member_access_denied_email(member_source_type, source_id, user_id)
   
human_name = @source_hidden ? 'Hidden' : member_source.human_name human_name = @source_hidden ? 'Hidden' : member_source.human_name
   
member_email_with_layout( email_with_layout(
to: user.notification_email_for(notification_group), to: user.notification_email_for(notification_group),
subject: subject("Access to the #{human_name} #{member_source.model_name.singular} was denied")) subject: subject("Access to the #{human_name} #{member_source.model_name.singular} was denied"))
end end
Loading
@@ -83,7 +83,7 @@ def member_invited_reminder_email(member_source_type, member_id, token, reminder
Loading
@@ -83,7 +83,7 @@ def member_invited_reminder_email(member_source_type, member_id, token, reminder
   
subject_line = subjects[reminder_index] % { inviter: member.created_by.name } subject_line = subjects[reminder_index] % { inviter: member.created_by.name }
   
member_email_with_layout( email_with_layout(
layout: 'unknown_user_mailer', layout: 'unknown_user_mailer',
to: member.invite_email, to: member.invite_email,
subject: subject(subject_line) subject: subject(subject_line)
Loading
@@ -97,7 +97,7 @@ def member_invite_accepted_email(member_source_type, member_id)
Loading
@@ -97,7 +97,7 @@ def member_invite_accepted_email(member_source_type, member_id)
return unless member_exists? return unless member_exists?
return unless member.created_by return unless member.created_by
   
member_email_with_layout( email_with_layout(
to: member.created_by.notification_email_for(notification_group), to: member.created_by.notification_email_for(notification_group),
subject: subject('Invitation accepted')) subject: subject('Invitation accepted'))
end end
Loading
@@ -111,7 +111,7 @@ def member_invite_declined_email(member_source_type, source_id, invite_email, cr
Loading
@@ -111,7 +111,7 @@ def member_invite_declined_email(member_source_type, source_id, invite_email, cr
   
user = User.find(created_by_id) user = User.find(created_by_id)
   
member_email_with_layout( email_with_layout(
to: user.notification_email_for(notification_group), to: user.notification_email_for(notification_group),
subject: subject('Invitation declined')) subject: subject('Invitation declined'))
end end
Loading
@@ -128,7 +128,7 @@ def member_expiration_date_updated_email(member_source_type, member_id)
Loading
@@ -128,7 +128,7 @@ def member_expiration_date_updated_email(member_source_type, member_id)
_('Group membership expiration date removed') _('Group membership expiration date removed')
end end
   
member_email_with_layout( email_with_layout(
to: member.user.notification_email_for(notification_group), to: member.user.notification_email_for(notification_group),
subject: subject(subject)) subject: subject(subject))
end end
Loading
@@ -176,13 +176,6 @@ def member_exists?
Loading
@@ -176,13 +176,6 @@ def member_exists?
def member_source_class def member_source_class
@member_source_type.classify.constantize @member_source_type.classify.constantize
end end
def member_email_with_layout(to:, subject:, layout: 'mailer')
mail(to: to, subject: subject) do |format|
format.html { render layout: layout }
format.text { render layout: layout }
end
end
end end
end end
   
Loading
Loading
Loading
@@ -149,10 +149,9 @@ def merge_requests_csv_email(user, project, csv_data, export_status)
Loading
@@ -149,10 +149,9 @@ def merge_requests_csv_email(user, project, csv_data, export_status)
   
filename = "#{project.full_path.parameterize}_merge_requests_#{Date.current.iso8601}.csv" filename = "#{project.full_path.parameterize}_merge_requests_#{Date.current.iso8601}.csv"
attachments[filename] = { content: csv_data, mime_type: 'text/csv' } attachments[filename] = { content: csv_data, mime_type: 'text/csv' }
mail(to: user.notification_email_for(@project.group), subject: subject("Exported merge requests")) do |format| email_with_layout(
format.html { render layout: 'mailer' } to: user.notification_email_for(@project.group),
format.text { render layout: 'mailer' } subject: subject("Exported merge requests"))
end
end end
   
def approved_merge_request_email(recipient_id, merge_request_id, approved_by_user_id, reason = nil) def approved_merge_request_email(recipient_id, merge_request_id, approved_by_user_id, reason = nil)
Loading
Loading
Loading
@@ -30,11 +30,9 @@ def pipeline_mail(pipeline, recipient, status)
Loading
@@ -30,11 +30,9 @@ def pipeline_mail(pipeline, recipient, status)
   
add_headers add_headers
   
mail(to: recipient, email_with_layout(
subject: subject(pipeline_subject(status))) do |format| to: recipient,
format.html { render layout: 'mailer' } subject: subject(pipeline_subject(status)))
format.text { render layout: 'mailer' }
end
end end
   
def add_headers def add_headers
Loading
Loading
Loading
@@ -13,7 +13,7 @@ def instance_access_request_email(user, recipient)
Loading
@@ -13,7 +13,7 @@ def instance_access_request_email(user, recipient)
@user = user @user = user
@recipient = recipient @recipient = recipient
   
profile_email_with_layout( email_with_layout(
to: recipient.notification_email_or_default, to: recipient.notification_email_or_default,
subject: subject(_("GitLab Account Request"))) subject: subject(_("GitLab Account Request")))
end end
Loading
@@ -21,7 +21,7 @@ def instance_access_request_email(user, recipient)
Loading
@@ -21,7 +21,7 @@ def instance_access_request_email(user, recipient)
def user_admin_rejection_email(name, email) def user_admin_rejection_email(name, email)
@name = name @name = name
   
profile_email_with_layout( email_with_layout(
to: email, to: email,
subject: subject(_("GitLab account request rejected"))) subject: subject(_("GitLab account request rejected")))
end end
Loading
@@ -29,7 +29,7 @@ def user_admin_rejection_email(name, email)
Loading
@@ -29,7 +29,7 @@ def user_admin_rejection_email(name, email)
def user_deactivated_email(name, email) def user_deactivated_email(name, email)
@name = name @name = name
   
profile_email_with_layout( email_with_layout(
to: email, to: email,
subject: subject(_('Your account has been deactivated'))) subject: subject(_('Your account has been deactivated')))
end end
Loading
@@ -125,7 +125,7 @@ def unknown_sign_in_email(user, ip, time)
Loading
@@ -125,7 +125,7 @@ def unknown_sign_in_email(user, ip, time)
@target_url = edit_profile_password_url @target_url = edit_profile_password_url
   
Gitlab::I18n.with_locale(@user.preferred_language) do Gitlab::I18n.with_locale(@user.preferred_language) do
profile_email_with_layout( email_with_layout(
to: @user.notification_email_or_default, to: @user.notification_email_or_default,
subject: subject(_("%{host} sign-in from new location") % { host: Gitlab.config.gitlab.host })) subject: subject(_("%{host} sign-in from new location") % { host: Gitlab.config.gitlab.host }))
end end
Loading
@@ -151,15 +151,6 @@ def new_email_address_added_email(user, email)
Loading
@@ -151,15 +151,6 @@ def new_email_address_added_email(user, email)
mail(to: @user.notification_email_or_default, subject: subject(_("New email address added"))) mail(to: @user.notification_email_or_default, subject: subject(_("New email address added")))
end end
end end
private
def profile_email_with_layout(to:, subject:, layout: 'mailer')
mail(to: to, subject: subject) do |format|
format.html { render layout: layout }
format.text { render layout: layout }
end
end
end end
end end
   
Loading
Loading
Loading
@@ -75,11 +75,9 @@ def inactive_project_deletion_warning_email(project, user, deletion_date)
Loading
@@ -75,11 +75,9 @@ def inactive_project_deletion_warning_email(project, user, deletion_date)
subject_text = "Action required: Project #{project.name} is scheduled to be deleted on " \ subject_text = "Action required: Project #{project.name} is scheduled to be deleted on " \
"#{deletion_date} due to inactivity" "#{deletion_date} due to inactivity"
   
mail(to: user.notification_email_for(project.group), email_with_layout(
subject: subject(subject_text)) do |format| to: user.notification_email_for(project.group),
format.html { render layout: 'mailer' } subject: subject(subject_text))
format.text { render layout: 'mailer' }
end
end end
   
private private
Loading
Loading
Loading
@@ -222,6 +222,13 @@ def add_unsubscription_headers_and_links
Loading
@@ -222,6 +222,13 @@ def add_unsubscription_headers_and_links
headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',') headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',')
@unsubscribe_url = unsubscribe_sent_notification_url(@sent_notification) @unsubscribe_url = unsubscribe_sent_notification_url(@sent_notification)
end end
def email_with_layout(to:, subject:, layout: 'mailer')
mail(to: to, subject: subject) do |format|
format.html { render layout: layout }
format.text { render layout: layout }
end
end
end end
   
Notify.prepend_mod_with('Notify') Notify.prepend_mod_with('Notify')
Loading
@@ -205,6 +205,10 @@ def inactive_project_deletion_warning
Loading
@@ -205,6 +205,10 @@ def inactive_project_deletion_warning
Notify.inactive_project_deletion_warning_email(project, user, '2022-04-22').message Notify.inactive_project_deletion_warning_email(project, user, '2022-04-22').message
end end
   
def user_auto_banned_email
::Notify.user_auto_banned_email(user.id, user.id, max_project_downloads: 5, within_seconds: 600).message
end
private private
   
def project def project
Loading
Loading
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
- link_end = '</a>'.html_safe
= email_default_heading(_("We've detected some unusual activity"))
%p
= _('We want to let you know %{username} has been banned from your GitLab instance due to them downloading more than %{max_project_downloads} project repositories within %{within_minutes} minutes.') % { username: sanitize_name(@user.name), max_project_downloads: @max_project_downloads, within_minutes: @within_minutes }
%p
= _('If this is a mistake, you can %{link_start}unban them%{link_end}.').html_safe % { link_start: link_start % { url: admin_users_url(filter: 'banned') }, link_end: link_end }
%p
= _('You can adjust rules on auto-banning %{link_start}here%{link_end}.').html_safe % { link_start: link_start % { url: network_admin_application_settings_url(anchor: 'js-ip-limits-settings') }, link_end: link_end }
<%= _("We've detected some unusual activity") %>
<%= _('We want to let you know %{username} has been banned from your GitLab instance due to them downloading more than %{max_project_downloads} project repositories within %{within_minutes} minutes.') % { username: sanitize_name(@user.name), max_project_downloads: @max_project_downloads, within_minutes: @within_minutes } %>
<%= _('If this is a mistake, you can unban them: %{url}.') % { url: admin_users_url(filter: 'banned') } %>
<%= _('You can adjust rules on auto-banning here: %{url}.') % { url: network_admin_application_settings_url(anchor: 'js-ip-limits-settings') } %>
Loading
@@ -10,7 +10,7 @@ def provisioned_member_access_granted_email(member_id)
Loading
@@ -10,7 +10,7 @@ def provisioned_member_access_granted_email(member_id)
   
@user = member.user @user = member.user
   
member_email_with_layout( email_with_layout(
to: member.user.email, to: member.user.email,
subject: subject("Welcome to GitLab")) subject: subject("Welcome to GitLab"))
end end
Loading
Loading
Loading
@@ -36,10 +36,9 @@ def user_escalation_rule_deleted_email(user, project, rules, recipient)
Loading
@@ -36,10 +36,9 @@ def user_escalation_rule_deleted_email(user, project, rules, recipient)
@project = project @project = project
@rules = rules @rules = rules
   
mail(to: recipient.notification_email_for(@project.group), subject: subject('User removed from escalation policy')) do |format| email_with_layout(
format.html { render layout: 'mailer' } to: recipient.notification_email_for(@project.group),
format.text { render layout: 'mailer' } subject: subject('User removed from escalation policy'))
end
end end
   
def incident_escalation_fired_email(project, user, issue) def incident_escalation_fired_email(project, user, issue)
Loading
Loading
Loading
@@ -7,10 +7,9 @@ def memberships_export_email(csv_data:, requested_by:, group:)
Loading
@@ -7,10 +7,9 @@ def memberships_export_email(csv_data:, requested_by:, group:)
   
filename = "#{group.full_path.parameterize}_group_memberships_#{Date.current.iso8601}.csv" filename = "#{group.full_path.parameterize}_group_memberships_#{Date.current.iso8601}.csv"
attachments[filename] = { content: csv_data, mime_type: 'text/csv' } attachments[filename] = { content: csv_data, mime_type: 'text/csv' }
mail(to: requested_by.notification_email_for(group), subject: "Exported group membership list") do |format| email_with_layout(
format.html { render layout: 'mailer' } to: requested_by.notification_email_for(group),
format.text { render layout: 'mailer' } subject: "Exported group membership list")
end
end end
end end
end end
Loading
@@ -10,10 +10,9 @@ def user_removed_from_rotation_email(user, rotation, recipients)
Loading
@@ -10,10 +10,9 @@ def user_removed_from_rotation_email(user, rotation, recipients)
@schedule = rotation.schedule @schedule = rotation.schedule
@project = rotation.project @project = rotation.project
   
mail(to: recipients.map(&:email), subject: subject('User removed from On-call rotation')) do |format| email_with_layout(
format.html { render layout: 'mailer' } to: recipients.map(&:email),
format.text { render layout: 'mailer' } subject: subject('User removed from On-call rotation'))
end
end end
end end
end end
Loading
@@ -7,7 +7,9 @@ def import_requirements_csv_email(user_id, project_id, results)
Loading
@@ -7,7 +7,9 @@ def import_requirements_csv_email(user_id, project_id, results)
@project = Project.find(project_id) @project = Project.find(project_id)
@results = results @results = results
   
requirement_email_with_layout(@user, @project.group, _('Imported requirements')) email_with_layout(
to: @user.notification_email_for(@project.group),
subject: subject(_('Imported requirements')))
end end
   
def requirements_csv_email(user, project, csv_data, export_status) def requirements_csv_email(user, project, csv_data, export_status)
Loading
@@ -18,14 +20,9 @@ def requirements_csv_email(user, project, csv_data, export_status)
Loading
@@ -18,14 +20,9 @@ def requirements_csv_email(user, project, csv_data, export_status)
filename = "#{project.full_path.parameterize}_requirements_#{Date.current.iso8601}.csv" filename = "#{project.full_path.parameterize}_requirements_#{Date.current.iso8601}.csv"
attachments[filename] = { content: csv_data, mime_type: 'text/csv' } attachments[filename] = { content: csv_data, mime_type: 'text/csv' }
   
requirement_email_with_layout(user, @project.group, _('Exported requirements')) email_with_layout(
end to: user.notification_email_for(@project.group),
subject: subject(_('Exported requirements')))
def requirement_email_with_layout(user, group, subj)
mail(to: user.notification_email_for(group), subject: subject(subj)) do |format|
format.html { render layout: 'mailer' }
format.text { render layout: 'mailer' }
end
end end
end end
end end
Loading
@@ -19162,6 +19162,12 @@ msgstr ""
Loading
@@ -19162,6 +19162,12 @@ msgstr ""
msgid "If this email was added in error, you can remove it here: %{profile_emails_url}" msgid "If this email was added in error, you can remove it here: %{profile_emails_url}"
msgstr "" msgstr ""
   
msgid "If this is a mistake, you can %{link_start}unban them%{link_end}."
msgstr ""
msgid "If this is a mistake, you can unban them: %{url}."
msgstr ""
msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}." msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}."
msgstr "" msgstr ""
   
Loading
@@ -42441,6 +42447,9 @@ msgstr ""
Loading
@@ -42441,6 +42447,9 @@ msgstr ""
msgid "We want to be sure it is you, please confirm you are not a robot." msgid "We want to be sure it is you, please confirm you are not a robot."
msgstr "" msgstr ""
   
msgid "We want to let you know %{username} has been banned from your GitLab instance due to them downloading more than %{max_project_downloads} project repositories within %{within_minutes} minutes."
msgstr ""
msgid "We will notify %{inviter} that you declined their invitation to join GitLab. You will stop receiving reminders." msgid "We will notify %{inviter} that you declined their invitation to join GitLab. You will stop receiving reminders."
msgstr "" msgstr ""
   
Loading
@@ -42456,6 +42465,12 @@ msgstr ""
Loading
@@ -42456,6 +42465,12 @@ msgstr ""
msgid "We're experiencing difficulties and this tab content is currently unavailable." msgid "We're experiencing difficulties and this tab content is currently unavailable."
msgstr "" msgstr ""
   
msgid "We've detected some unusual activity"
msgstr ""
msgid "We've detected unusual activity"
msgstr ""
msgid "We've found no vulnerabilities" msgid "We've found no vulnerabilities"
msgstr "" msgstr ""
   
Loading
@@ -43276,6 +43291,12 @@ msgstr ""
Loading
@@ -43276,6 +43291,12 @@ msgstr ""
msgid "You can %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}." msgid "You can %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}."
msgstr "" msgstr ""
   
msgid "You can adjust rules on auto-banning %{link_start}here%{link_end}."
msgstr ""
msgid "You can adjust rules on auto-banning here: %{url}."
msgstr ""
msgid "You can also create a project from the command line." msgid "You can also create a project from the command line."
msgstr "" msgstr ""
   
Loading
Loading
Loading
@@ -3,9 +3,62 @@
Loading
@@ -3,9 +3,62 @@
require 'spec_helper' require 'spec_helper'
   
RSpec.describe Emails::AdminNotification do RSpec.describe Emails::AdminNotification do
include EmailSpec::Matchers
include_context 'gitlab email notification'
it 'adds email methods to Notify' do it 'adds email methods to Notify' do
subject.instance_methods.each do |email_method| subject.instance_methods.each do |email_method|
expect(Notify).to be_respond_to(email_method) expect(Notify).to be_respond_to(email_method)
end end
end end
describe 'user_auto_banned_email' do
let_it_be(:admin) { create(:user) }
let_it_be(:user) { create(:user) }
let(:max_project_downloads) { 5 }
let(:time_period) { 600 }
subject do
Notify.user_auto_banned_email(
admin.id, user.id,
max_project_downloads: max_project_downloads,
within_seconds: time_period
)
end
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'appearance header and footer enabled'
it_behaves_like 'appearance header and footer not enabled'
it 'is sent to the administrator' do
is_expected.to deliver_to admin.email
end
it 'has the correct subject' do
is_expected.to have_subject "We've detected unusual activity"
end
it 'includes the name of the user' do
is_expected.to have_body_text user.name
end
it 'includes the reason' do
is_expected.to have_body_text "due to them downloading more than 5 project repositories within 10 minutes"
end
it 'includes a link to unban the user' do
is_expected.to have_body_text admin_users_url(filter: 'banned')
end
it 'includes a link to change the settings' do
is_expected.to have_body_text network_admin_application_settings_url(anchor: 'js-ip-limits-settings')
end
it 'includes the email reason' do
is_expected.to have_body_text "You're receiving this email because of your account on localhost"
end
end
end end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment