Commit 85ffd85a authored by Sean McGivern's avatar Sean McGivern

Merge branch '29483-spam-check-only-title-and-description' into 'master'

Spam check only when Snippet / Issue "title" or "description" are changed

Closes #29483 and #29645

See merge request !10104
parents 78840333 d730b69e
...@@ -148,7 +148,14 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -148,7 +148,14 @@ class Projects::IssuesController < Projects::ApplicationController
end end
format.json do format.json do
render json: @issue.to_json(include: { milestone: {}, assignee: { only: [:name, :username], methods: [:avatar_url] }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) if @issue.valid?
render json: @issue.to_json(methods: [:task_status, :task_status_short],
include: { milestone: {},
assignee: { only: [:name, :username], methods: [:avatar_url] },
labels: { methods: :text_color } })
else
render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity
end
end end
end end
......
...@@ -41,7 +41,7 @@ module Spammable ...@@ -41,7 +41,7 @@ module Spammable
def check_for_spam def check_for_spam
error_msg = if Gitlab::Recaptcha.enabled? error_msg = if Gitlab::Recaptcha.enabled?
"Your #{spammable_entity_type} has been recognized as spam. "\ "Your #{spammable_entity_type} has been recognized as spam. "\
"You can still submit it by solving Captcha." "Please, change the content or solve the reCAPTCHA to proceed."
else else
"Your #{spammable_entity_type} has been recognized as spam and has been discarded." "Your #{spammable_entity_type} has been recognized as spam and has been discarded."
end end
......
...@@ -211,9 +211,8 @@ class Issue < ActiveRecord::Base ...@@ -211,9 +211,8 @@ class Issue < ActiveRecord::Base
due_date.try(:past?) || false due_date.try(:past?) || false
end end
# Only issues on public projects should be checked for spam
def check_for_spam? def check_for_spam?
project.public? project.public? && (title_changed? || description_changed?)
end end
def as_json(options = {}) def as_json(options = {})
......
...@@ -132,7 +132,8 @@ class Snippet < ActiveRecord::Base ...@@ -132,7 +132,8 @@ class Snippet < ActiveRecord::Base
end end
def check_for_spam? def check_for_spam?
public? visibility_level_changed?(to: Snippet::PUBLIC) ||
(public? && (title_changed? || content_changed?))
end end
def spammable_entity_type def spammable_entity_type
......
...@@ -14,6 +14,9 @@ module SpamCheckService ...@@ -14,6 +14,9 @@ module SpamCheckService
@spam_log_id = params.delete(:spam_log_id) @spam_log_id = params.delete(:spam_log_id)
end end
# In order to be proceed to the spam check process, @spammable has to be
# a dirty instance, which means it should be already assigned with the new
# attribute values.
def spam_check(spammable, user) def spam_check(spammable, user)
spam_service = SpamService.new(spammable, @request) spam_service = SpamService.new(spammable, @request)
......
---
title: Spam check only when spammable attributes have changed
merge_request:
author:
...@@ -241,10 +241,27 @@ describe Projects::IssuesController do ...@@ -241,10 +241,27 @@ describe Projects::IssuesController do
expect(spam_logs.first.recaptcha_verified).to be_falsey expect(spam_logs.first.recaptcha_verified).to be_falsey
end end
it 'renders verify template' do context 'as HTML' do
update_spam_issue it 'renders verify template' do
update_spam_issue
expect(response).to render_template(:verify)
end
end
context 'as JSON' do
before do
update_issue({ title: 'Spam Title', description: 'Spam lives here' }, format: :json)
end
it 'renders json errors' do
expect(json_response)
.to eql("errors" => ["Your issue has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."])
end
expect(response).to render_template(:verify) it 'returns 422 status' do
expect(response).to have_http_status(422)
end
end end
end end
......
...@@ -670,4 +670,41 @@ describe Issue, models: true do ...@@ -670,4 +670,41 @@ describe Issue, models: true do
expect(attrs_hash).to include('time_estimate') expect(attrs_hash).to include('time_estimate')
end end
end end
describe '#check_for_spam' do
let(:project) { create :project, visibility_level: visibility_level }
let(:issue) { create :issue, project: project }
subject do
issue.assign_attributes(description: description)
issue.check_for_spam?
end
context 'when project is public and spammable attributes changed' do
let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
let(:description) { 'woo' }
it 'returns true' do
is_expected.to be_truthy
end
end
context 'when project is private' do
let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
let(:description) { issue.description }
it 'returns false' do
is_expected.to be_falsey
end
end
context 'when spammable attributes have not changed' do
let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
let(:description) { issue.description }
it 'returns false' do
is_expected.to be_falsey
end
end
end
end end
...@@ -198,4 +198,47 @@ describe Snippet, models: true do ...@@ -198,4 +198,47 @@ describe Snippet, models: true do
expect(snippet.participants).to include(note1.author, note2.author) expect(snippet.participants).to include(note1.author, note2.author)
end end
end end
describe '#check_for_spam' do
let(:snippet) { create :snippet, visibility_level: visibility_level }
subject do
snippet.assign_attributes(title: title)
snippet.check_for_spam?
end
context 'when public and spammable attributes changed' do
let(:visibility_level) { Snippet::PUBLIC }
let(:title) { 'woo' }
it 'returns true' do
is_expected.to be_truthy
end
end
context 'when private' do
let(:visibility_level) { Snippet::PRIVATE }
let(:title) { snippet.title }
it 'returns false' do
is_expected.to be_falsey
end
it 'returns true when switching to public' do
snippet.save!
snippet.visibility_level = Snippet::PUBLIC
expect(snippet.check_for_spam?).to be_truthy
end
end
context 'when spammable attributes have not changed' do
let(:visibility_level) { Snippet::PUBLIC }
let(:title) { snippet.title }
it 'returns false' do
is_expected.to be_falsey
end
end
end
end end
...@@ -19,42 +19,67 @@ describe SpamService, services: true do ...@@ -19,42 +19,67 @@ describe SpamService, services: true do
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:request) { double(:request, env: {}) } let(:request) { double(:request, env: {}) }
context 'when indicated as spam by akismet' do context 'when spammable attributes have not changed' do
before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: true)) } before do
issue.closed_at = Time.zone.now
it 'doesnt check as spam when request is missing' do allow(AkismetService).to receive(:new).and_return(double(is_spam?: true))
check_spam(issue, nil, false)
expect(issue.spam).to be_falsey
end end
it 'checks as spam' do it 'returns false' do
check_spam(issue, request, false) expect(check_spam(issue, request, false)).to be_falsey
expect(issue.spam).to be_truthy
end end
it 'creates a spam log' do it 'does not create a spam log' do
expect { check_spam(issue, request, false) } expect { check_spam(issue, request, false) }
.to change { SpamLog.count }.from(0).to(1) .not_to change { SpamLog.count }
end end
end
it 'doesnt yield block' do context 'when spammable attributes have changed' do
expect(check_spam(issue, request, false)) before do
.to eql(SpamLog.last) issue.description = 'SPAM!'
end end
end
context 'when not indicated as spam by akismet' do context 'when indicated as spam by akismet' do
before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) } before do
allow(AkismetService).to receive(:new).and_return(double(is_spam?: true))
end
it 'returns false' do it 'doesnt check as spam when request is missing' do
expect(check_spam(issue, request, false)).to be_falsey check_spam(issue, nil, false)
expect(issue.spam).to be_falsey
end
it 'checks as spam' do
check_spam(issue, request, false)
expect(issue.spam).to be_truthy
end
it 'creates a spam log' do
expect { check_spam(issue, request, false) }
.to change { SpamLog.count }.from(0).to(1)
end
it 'doesnt yield block' do
expect(check_spam(issue, request, false))
.to eql(SpamLog.last)
end
end end
it 'does not create a spam log' do context 'when not indicated as spam by akismet' do
expect { check_spam(issue, request, false) } before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) }
.not_to change { SpamLog.count }
it 'returns false' do
expect(check_spam(issue, request, false)).to be_falsey
end
it 'does not create a spam log' do
expect { check_spam(issue, request, false) }
.not_to change { SpamLog.count }
end
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment