Commit ad6473de authored by Sean McGivern's avatar Sean McGivern Committed by Ruben Davila

Merge branch '28093-snippet-and-issue-spam-check-on-edit' into 'master'

Spam check and reCAPTCHA improvements

Closes #28093

See merge request !9248
Conflicts:
	app/services/issues/create_service.rb
parent 26e77c38
......@@ -17,13 +17,31 @@ module SpammableActions
private
def recaptcha_params
return {} unless params[:recaptcha_verification] && Gitlab::Recaptcha.load_configurations! && verify_recaptcha
def recaptcha_check_with_fallback(&fallback)
if spammable.valid?
redirect_to spammable
elsif render_recaptcha?
if params[:recaptcha_verification]
flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
end
render :verify
else
fallback.call
end
end
def spammable_params
default_params = { request: request }
recaptcha_check = params[:recaptcha_verification] &&
Gitlab::Recaptcha.load_configurations! &&
verify_recaptcha
return default_params unless recaptcha_check
{
recaptcha_verified: true,
spam_log_id: params[:spam_log_id]
}
{ recaptcha_verified: true,
spam_log_id: params[:spam_log_id] }.merge(default_params)
end
def spammable
......
......@@ -91,15 +91,15 @@ class Projects::IssuesController < Projects::ApplicationController
end
def create
extra_params = { request: request,
merge_request_for_resolving_discussions: merge_request_for_resolving_discussions }
extra_params.merge!(recaptcha_params)
create_params = issue_params
.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
.merge(spammable_params)
@issue = Issues::CreateService.new(project, current_user, issue_params.merge(extra_params)).execute
@issue = Issues::CreateService.new(project, current_user, create_params).execute
respond_to do |format|
format.html do
html_response_create
recaptcha_check_with_fallback { render :new }
end
format.js do
@link = @issue.attachment.url.to_js
......@@ -108,7 +108,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
def update
@issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue)
update_params = issue_params.merge(spammable_params)
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id])
......@@ -120,11 +122,7 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to do |format|
format.html do
if @issue.valid?
redirect_to issue_path(@issue)
else
render :edit
end
recaptcha_check_with_fallback { render :edit }
end
format.json do
......@@ -176,20 +174,6 @@ class Projects::IssuesController < Projects::ApplicationController
protected
def html_response_create
if @issue.valid?
redirect_to issue_path(@issue)
elsif render_recaptcha?
if params[:recaptcha_verification]
flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
end
render :verify
else
render :new
end
end
def issue
# The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old
......
......@@ -37,27 +37,22 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def create
create_params = snippet_params.merge(request: request)
create_params = snippet_params.merge(spammable_params)
@snippet = CreateSnippetService.new(@project, current_user, create_params).execute
if @snippet.valid?
respond_with(@snippet,
location: namespace_project_snippet_path(@project.namespace,
@project, @snippet))
else
render :new
end
recaptcha_check_with_fallback { render :new }
end
def edit
end
def update
UpdateSnippetService.new(project, current_user, @snippet,
snippet_params).execute
respond_with(@snippet,
location: namespace_project_snippet_path(@project.namespace,
@project, @snippet))
update_params = snippet_params.merge(spammable_params)
UpdateSnippetService.new(project, current_user, @snippet, update_params).execute
recaptcha_check_with_fallback { render :edit }
end
def show
......
......@@ -41,19 +41,22 @@ class SnippetsController < ApplicationController
end
def create
create_params = snippet_params.merge(request: request)
create_params = snippet_params.merge(spammable_params)
@snippet = CreateSnippetService.new(nil, current_user, create_params).execute
respond_with @snippet.becomes(Snippet)
recaptcha_check_with_fallback { render :new }
end
def edit
end
def update
UpdateSnippetService.new(nil, current_user, @snippet,
snippet_params).execute
respond_with @snippet.becomes(Snippet)
update_params = snippet_params.merge(spammable_params)
UpdateSnippetService.new(nil, current_user, @snippet, update_params).execute
recaptcha_check_with_fallback { render :edit }
end
def show
......
......@@ -13,7 +13,7 @@ module Spammable
attr_accessor :spam
attr_accessor :spam_log
after_validation :check_for_spam, on: :create
after_validation :check_for_spam, on: [:create, :update]
cattr_accessor :spammable_attrs, instance_accessor: false do
[]
......
......@@ -9,8 +9,4 @@ class ProjectSnippet < Snippet
participant :author
participant :notes_with_associations
def check_for_spam?
super && project.public?
end
end
class CreateSnippetService < BaseService
include SpamCheckService
def execute
request = params.delete(:request)
api = params.delete(:api)
filter_spam_check_params
snippet = if project
project.snippets.build(params)
......@@ -15,10 +16,11 @@ class CreateSnippetService < BaseService
end
snippet.author = current_user
snippet.spam = SpamService.new(snippet, request).check(api)
spam_check(snippet, current_user)
if snippet.save
UserAgentDetailService.new(snippet, request).create
UserAgentDetailService.new(snippet, @request).create
end
snippet
......
......@@ -191,14 +191,12 @@ class IssuableBaseService < BaseService
# To be overridden by subclasses
end
def after_update(issuable)
def before_update(issuable)
# To be overridden by subclasses
end
def update_issuable(issuable, attributes)
issuable.with_transaction_returning_status do
issuable.update(attributes.merge(updated_by: current_user))
end
def after_update(issuable)
# To be overridden by subclasses
end
def update(issuable)
......@@ -212,16 +210,22 @@ class IssuableBaseService < BaseService
label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids)
params[:label_ids] = label_ids if labels_changing?(issuable.label_ids, label_ids)
if params.present? && update_issuable(issuable, params)
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
handle_common_system_notes(issuable, old_labels: old_labels)
end
if params.present?
issuable.assign_attributes(params.merge(updated_by: current_user))
before_update(issuable)
handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
after_update(issuable)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
if issuable.with_transaction_returning_status { issuable.save }
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
handle_common_system_notes(issuable, old_labels: old_labels)
end
handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
after_update(issuable)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
end
end
issuable
......
module Issues
class CreateService < Issues::BaseService
include SpamCheckService
def execute
@request = params.delete(:request)
@api = params.delete(:api)
@recaptcha_verified = params.delete(:recaptcha_verified)
@spam_log_id = params.delete(:spam_log_id)
filter_spam_check_params
issue_attributes = params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
@issue = BuildService.new(project, current_user, issue_attributes).execute
......@@ -12,14 +11,8 @@ module Issues
create(@issue)
end
def before_create(issuable)
if @recaptcha_verified
spam_log = current_user.spam_logs.find_by(id: @spam_log_id, title: issuable.title)
spam_log.update!(recaptcha_verified: true) if spam_log
else
issuable.spam = spam_service.check(@api)
issuable.spam_log = spam_service.spam_log
end
def before_create(issue)
spam_check(issue, current_user)
end
def after_create(issuable)
......@@ -42,10 +35,6 @@ module Issues
private
def spam_service
@spam_service ||= SpamService.new(@issue, @request)
end
def user_agent_detail_service
UserAgentDetailService.new(@issue, @request)
end
......
module Issues
class UpdateService < Issues::BaseService
include SpamCheckService
def execute(issue)
filter_spam_check_params
update(issue)
end
def before_update(issue)
spam_check(issue, current_user)
end
def handle_changes(issue, old_labels: [], old_mentioned_users: [])
if has_changes?(issue, old_labels: old_labels)
todo_service.mark_pending_todos_as_done(issue, current_user)
......
# SpamCheckService
#
# Provide helper methods for checking if a given spammable object has
# potential spam data.
#
# Dependencies:
# - params with :request
#
module SpamCheckService
def filter_spam_check_params
@request = params.delete(:request)
@api = params.delete(:api)
@recaptcha_verified = params.delete(:recaptcha_verified)
@spam_log_id = params.delete(:spam_log_id)
end
def spam_check(spammable, user)
spam_service = SpamService.new(spammable, @request)
spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do
user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
end
end
end
......@@ -17,15 +17,6 @@ class SpamService
end
end
def check(api = false)
return false unless request && check_for_spam?
return false unless akismet.is_spam?
create_spam_log(api)
true
end
def mark_as_spam!
return false unless spammable.submittable_as_spam?
......@@ -36,8 +27,30 @@ class SpamService
end
end
def when_recaptcha_verified(recaptcha_verified, api = false)
# In case it's a request which is already verified through recaptcha, yield
# block.
if recaptcha_verified
yield
else
# Otherwise, it goes to Akismet and check if it's a spam. If that's the
# case, it assigns spammable record as "spam" and create a SpamLog record.
spammable.spam = check(api)
spammable.spam_log = spam_log
end
end
private
def check(api)
return false unless request && check_for_spam?
return false unless akismet.is_spam?
create_spam_log(api)
true
end
def akismet
@akismet ||= AkismetService.new(
spammable_owner,
......
class UpdateSnippetService < BaseService
include SpamCheckService
attr_accessor :snippet
def initialize(project, user, snippet, params)
......@@ -9,7 +11,7 @@ class UpdateSnippetService < BaseService
def execute
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != snippet.visibility_level
unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(snippet, new_visibility)
......@@ -17,6 +19,10 @@ class UpdateSnippetService < BaseService
end
end
snippet.update_attributes(params)
filter_spam_check_params
snippet.assign_attributes(params)
spam_check(snippet, current_user)
snippet.save
end
end
- humanized_resource_name = spammable.class.model_name.human.downcase
- resource_name = spammable.class.model_name.singular
%h3.page-title
Anti-spam verification
%hr
%p
#{"We detected potential spam in the #{humanized_resource_name}. Please solve the reCAPTCHA to proceed."}
= form_for form do |f|
.recaptcha
- params[resource_name].each do |field, value|
= hidden_field(resource_name, field, value: value)
= hidden_field_tag(:spam_log_id, spammable.spam_log.id)
= hidden_field_tag(:recaptcha_verification, true)
= recaptcha_tags
-# Yields a block with given extra params.
= yield
.row-content-block.footer-block
= f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
- page_title "Anti-spam verification"
- form = [@project.namespace.becomes(Namespace), @project, @issue]
%h3.page-title
Anti-spam verification
%hr
%p
We detected potential spam in the issue description. Please verify that you are not a robot to submit the issue.
= form_for [@project.namespace.becomes(Namespace), @project, @issue] do |f|
.recaptcha
- params[:issue].each do |field, value|
= hidden_field(:issue, field, value: value)
= hidden_field_tag(:merge_request_for_resolving_discussions, params[:merge_request_for_resolving_discussions])
= hidden_field_tag(:spam_log_id, @issue.spam_log.id)
= hidden_field_tag(:recaptcha_verification, true)
= recaptcha_tags
.row-content-block.footer-block
= f.submit "Submit #{@issue.class.model_name.human.downcase}", class: 'btn btn-create'
= render layout: 'layouts/recaptcha_verification', locals: { spammable: @issue, form: form } do
= hidden_field_tag(:merge_request_for_resolving_discussions, params[:merge_request_for_resolving_discussions])
- form = [@project.namespace.becomes(Namespace), @project, @snippet.becomes(Snippet)]
= render 'layouts/recaptcha_verification', spammable: @snippet, form: form
- form = [@snippet.becomes(Snippet)]
= render 'layouts/recaptcha_verification', spammable: @snippet, form: form
---
title: Spam check and reCAPTCHA improvements
merge_request:
author:
......@@ -231,6 +231,10 @@ module API
end
end
def render_spam_error!
render_api_error!({ error: 'Spam detected' }, 400)
end
def render_api_error!(message, status)
error!({ 'message' => message }, status, header)
end
......
......@@ -177,9 +177,13 @@ module API
params.delete(:updated_at)
end
update_params = declared_params(include_missing: false).merge(request: request, api: true)
issue = ::Issues::UpdateService.new(user_project,
current_user,
declared_params(include_missing: false)).execute(issue)
update_params).execute(issue)
render_spam_error! if issue.spam?
if issue.valid?
present issue, with: Entities::Issue, current_user: current_user, project: user_project
......
......@@ -63,6 +63,8 @@ module API
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
render_spam_error! if snippet.spam?
if snippet.persisted?
present snippet, with: Entities::ProjectSnippet
else
......@@ -92,12 +94,16 @@ module API
authorize! :update_project_snippet, snippet
snippet_params = declared_params(include_missing: false)
.merge(request: request, api: true)
snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
UpdateSnippetService.new(user_project, current_user, snippet,
snippet_params).execute
if snippet.persisted?
render_spam_error! if snippet.spam?
if snippet.valid?
present snippet, with: Entities::ProjectSnippet
else
render_validation_error!(snippet)
......
......@@ -67,6 +67,8 @@ module API
attrs = declared_params(include_missing: false).merge(request: request, api: true)
snippet = CreateSnippetService.new(nil, current_user, attrs).execute
render_spam_error! if snippet.spam?
if snippet.persisted?
present snippet, with: Entities::PersonalSnippet
else
......@@ -93,9 +95,12 @@ module API
return not_found!('Snippet') unless snippet
authorize! :update_personal_snippet, snippet
attrs = declared_params(include_missing: false)
attrs = declared_params(include_missing: false).merge(request: request, api: true)
UpdateSnippetService.new(nil, current_user, snippet, attrs).execute
render_spam_error! if snippet.spam?
if snippet.persisted?
present snippet, with: Entities::PersonalSnippet
else
......
......@@ -148,9 +148,7 @@ module API
issue = ::Issues::CreateService.new(user_project,
current_user,
issue_params.merge(request: request, api: true)).execute
if issue.spam?
render_api_error!({ error: 'Spam detected' }, 400)
end
render_spam_error! if issue.spam?
if issue.valid?
present issue, with: ::API::Entities::Issue, current_user: current_user, project: user_project
......@@ -181,9 +179,13 @@ module API
params.delete(:updated_at)
end
update_params = declared_params(include_missing: false).merge(request: request, api: true)
issue = ::Issues::UpdateService.new(user_project,
current_user,
declared_params(include_missing: false)).execute(issue)
update_params).execute(issue)
render_spam_error! if issue.spam?
if issue.valid?
present issue, with: ::API::Entities::Issue, current_user: current_user, project: user_project
......
......@@ -64,6 +64,8 @@ module API
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
render_spam_error! if snippet.spam?
if snippet.persisted?
present snippet, with: ::API::V3::Entities::ProjectSnippet
else
......@@ -93,12 +95,16 @@ module API
authorize! :update_project_snippet, snippet
snippet_params = declared_params(include_missing: false)
.merge(request: request, api: true)
snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
UpdateSnippetService.new(user_project, current_user, snippet,
snippet_params).execute
if snippet.persisted?
render_spam_error! if snippet.spam?
if snippet.valid?
present snippet, with: ::API::V3::Entities::ProjectSnippet
else
render_validation_error!(snippet)
......
......@@ -150,6 +150,113 @@ describe Projects::IssuesController do
end
end
context 'Akismet is enabled' do
let(:project) { create(:project_empty_repo, :public) }
before do
stub_application_setting(recaptcha_enabled: true)
allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
end
context 'when an issue is not identified as spam' do
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(false)
end
it 'normally updates the issue' do
expect { update_issue(title: 'Foo') }.to change { issue.reload.title }.to('Foo')
end
end
context 'when an issue is identified as spam' do
before { allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) }
context 'when captcha is not verified' do
def update_spam_issue
update_issue(title: 'Spam Title', description: 'Spam lives here')
end
before { allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) }
it 'rejects an issue recognized as a spam' do
expect { update_spam_issue }.not_to change{ issue.reload.title }
end
it 'rejects an issue recognized as a spam when recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
expect { update_spam_issue }.not_to change{ issue.reload.title }
end
it 'creates a spam log' do
update_spam_issue
spam_logs = SpamLog.all
expect(spam_logs.count).to eq(1)
expect(spam_logs.first.title).to eq('Spam Title')
expect(spam_logs.first.recaptcha_verified).to be_falsey
end
it 'renders verify template' do
update_spam_issue
expect(response).to render_template(:verify)
end
end
context 'when captcha is verified' do
let(:spammy_title) { 'Whatever' }
let!(:spam_logs) { create_list(:spam_log, 2, user: user, title: spammy_title) }
def update_verified_issue
update_issue({ title: spammy_title },
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
end
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha)
.and_return(true)
end
it 'redirect to issue page' do
update_verified_issue
expect(response).
to redirect_to(namespace_project_issue_path(project.namespace, project, issue))
end
it 'accepts an issue after recaptcha is verified' do
expect{ update_verified_issue }.to change{ issue.reload.title }.to(spammy_title)
end
it 'marks spam log as recaptcha_verified' do
expect { update_verified_issue }.to change { SpamLog.last.recaptcha_verified }.from(false).to(true)
end
it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do
spam_log = create(:spam_log)
expect { update_issue(spam_log_id: spam_log.id, recaptcha_verification: true) }.
not_to change { SpamLog.last.recaptcha_verified }
end
end
end
end
def update_issue(issue_params = {}, additional_params = {})
params = {
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: issue.iid,
issue: issue_params
}.merge(additional_params)
put :update, params
end
def move_issue
put :update,
namespace_id: project.namespace.to_param,
......@@ -382,7 +489,7 @@ describe Projects::IssuesController do
allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
end
context 'when an issue is not identified as a spam' do
context 'when an issue is not identified as spam' do
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(false)
......@@ -393,7 +500,7 @@ describe Projects::IssuesController do
end
end
context 'when an issue is identified as a spam' do
context 'when an issue is identified as spam' do
before { allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) }
context 'when captcha is not verified' do
......
......@@ -70,7 +70,7 @@ describe Projects::SnippetsController do
end
describe 'POST #create' do
def create_snippet(project, snippet_params = {})
def create_snippet(project, snippet_params = {}, additional_params = {})
sign_in(user)
project.add_developer(user)
......@@ -79,7 +79,7 @@ describe Projects::SnippetsController do
namespace_id: project.namespace.to_param,
project_id: project.to_param,
project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}
}.merge(additional_params)
end
context 'when the snippet is spam' do
......@@ -87,35 +87,179 @@ describe Projects::SnippetsController do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the project is private' do
let(:private_project) { create(:project_empty_repo, :private) }
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
end
end
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to render_template(:new)
end
it 'creates a spam log' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
it 'renders :new with recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
create_snippet(project, visibility_level: Snippet::PUBLIC)
expect(response).to render_template(:new)
end
context 'when the snippet is public' do
it 'creates the snippet' do
expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
to change { Snippet.count }.by(1)
context 'recaptcha enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
end
it 'renders :verify with recaptcha enabled' do
create_snippet(project, visibility_level: Snippet::PUBLIC)
expect(response).to render_template(:verify)
end
it 'renders snippet page when recaptcha verified' do
spammy_title = 'Whatever'
spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
create_snippet(project,
{ visibility_level: Snippet::PUBLIC },
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
expect(response).to redirect_to(Snippet.last)
end
end
end
end
end
describe 'PUT #update' do
let(:project) { create :project, :public }
let(:snippet) { create :project_snippet, author: user, project: project, visibility_level: visibility_level }
def update_snippet(snippet_params = {}, additional_params = {})
sign_in(user)
project.add_developer(user)
put :update, {
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: snippet.id,
project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}.merge(additional_params)
snippet.reload
end
context 'when the snippet is spam' do
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the snippet is private' do
let(:visibility_level) { Snippet::PRIVATE }
it 'updates the snippet' do
expect { update_snippet(title: 'Foo') }.
to change { snippet.reload.title }.to('Foo')
end
end
context 'when the project is public' do
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
context 'when the snippet is public' do
let(:visibility_level) { Snippet::PUBLIC }
it 'rejects the shippet' do
expect { update_snippet(title: 'Foo') }.
not_to change { snippet.reload.title }
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo') }.
to change { SpamLog.count }.by(1)
end
it 'renders :edit with recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
update_snippet(title: 'Foo')
expect(response).to render_template(:edit)
end
context 'recaptcha enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
end
it 'renders :verify with recaptcha enabled' do
update_snippet(title: 'Foo')
expect(response).to render_template(:verify)
end
it 'renders snippet page when recaptcha verified' do
spammy_title = 'Whatever'
spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
snippet = update_snippet({ title: spammy_title },
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
expect(response).to redirect_to(snippet)
end
end
end
context 'when the private snippet is made public' do
let(:visibility_level) { Snippet::PRIVATE }
it 'rejects the shippet' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
not_to change { snippet.reload.title }
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
it 'renders :edit with recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to render_template(:new)
update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
expect(response).to render_template(:edit)
end
context 'recaptcha enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
end
it 'renders :verify with recaptcha enabled' do
update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
expect(response).to render_template(:verify)
end
it 'creates a spam log' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
it 'renders snippet page when recaptcha verified' do
spammy_title = 'Whatever'
spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
snippet = update_snippet({ title: spammy_title, visibility_level: Snippet::PUBLIC },
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
expect(response).to redirect_to(snippet)
end
end
end
......
......@@ -139,12 +139,14 @@ describe SnippetsController do
end
describe 'POST #create' do
def create_snippet(snippet_params = {})
def create_snippet(snippet_params = {}, additional_params = {})
sign_in(user)
post :create, {
personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}
}.merge(additional_params)
Snippet.last
end
context 'when the snippet is spam' do
......@@ -163,13 +165,164 @@ describe SnippetsController do
it 'rejects the shippet' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to render_template(:new)
end
it 'creates a spam log' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
it 'renders :new with recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
create_snippet(visibility_level: Snippet::PUBLIC)
expect(response).to render_template(:new)
end
context 'recaptcha enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
end
it 'renders :verify with recaptcha enabled' do
create_snippet(visibility_level: Snippet::PUBLIC)
expect(response).to render_template(:verify)
end
it 'renders snippet page when recaptcha verified' do
spammy_title = 'Whatever'
spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
snippet = create_snippet({ title: spammy_title },
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
expect(response).to redirect_to(snippet_path(snippet))
end
end
end
end
end
describe 'PUT #update' do
let(:project) { create :project }
let(:snippet) { create :personal_snippet, author: user, project: project, visibility_level: visibility_level }
def update_snippet(snippet_params = {}, additional_params = {})
sign_in(user)
put :update, {
id: snippet.id,
personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}.merge(additional_params)
snippet.reload
end
context 'when the snippet is spam' do
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the snippet is private' do
let(:visibility_level) { Snippet::PRIVATE }
it 'updates the snippet' do
expect { update_snippet(title: 'Foo') }.
to change { snippet.reload.title }.to('Foo')
end
end
context 'when a private snippet is made public' do
let(:visibility_level) { Snippet::PRIVATE }
it 'rejects the snippet' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
not_to change { snippet.reload.title }
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
it 'renders :edit with recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
expect(response).to render_template(:edit)
end
context 'recaptcha enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
end
it 'renders :verify with recaptcha enabled' do
update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
expect(response).to render_template(:verify)
end
it 'renders snippet page when recaptcha verified' do
spammy_title = 'Whatever'
spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
snippet = update_snippet({ title: spammy_title, visibility_level: Snippet::PUBLIC },
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
expect(response).to redirect_to(snippet)
end
end
end
context 'when the snippet is public' do
let(:visibility_level) { Snippet::PUBLIC }
it 'rejects the shippet' do
expect { update_snippet(title: 'Foo') }.
not_to change { snippet.reload.title }
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo') }.
to change { SpamLog.count }.by(1)
end
it 'renders :edit with recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
update_snippet(title: 'Foo')
expect(response).to render_template(:edit)
end
context 'recaptcha enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
end
it 'renders :verify with recaptcha enabled' do
update_snippet(title: 'Foo')
expect(response).to render_template(:verify)
end
it 'renders snippet page when recaptcha verified' do
spammy_title = 'Whatever'
spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
snippet = update_snippet({ title: spammy_title },
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
expect(response).to redirect_to(snippet_path(snippet))
end
end
end
end
end
......
......@@ -23,6 +23,7 @@ describe Issue, 'Spammable' do
describe '#check_for_spam?' do
it 'returns true for public project' do
issue.project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
expect(issue.check_for_spam?).to eq(true)
end
......
......@@ -919,6 +919,33 @@ describe API::Issues, api: true do
end
end
describe 'PUT /projects/:id/issues/:issue_id with spam filtering' do
let(:params) do
{
title: 'updated title',
description: 'content here',
labels: 'label, label2'
}
end
it "does not create a new project issue" do
allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
allow_any_instance_of(AkismetService).to receive_messages(is_spam?: true)
put api("/projects/#{project.id}/issues/#{issue.id}", user), params
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
spam_logs = SpamLog.all
expect(spam_logs.count).to eq(1)
expect(spam_logs[0].title).to eq('updated title')
expect(spam_logs[0].description).to eq('content here')
expect(spam_logs[0].user).to eq(user)
expect(spam_logs[0].noteable_type).to eq('Issue')
end
end
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
let!(:label) { create(:label, title: 'dummy', project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) }
......
......@@ -73,43 +73,33 @@ describe API::ProjectSnippets, api: true do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the project is private' do
let(:private_project) { create(:project_empty_repo, :private) }
context 'when the snippet is public' do
it 'creates the snippet' do
expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
to change { Snippet.count }.by(1)
end
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
end
end
context 'when the project is public' do
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
end
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
end
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to have_http_status(400)
end
it 'creates a spam log' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
it 'creates a spam log' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end
describe 'PUT /projects/:project_id/snippets/:id/' do
let(:snippet) { create(:project_snippet, author: admin) }
let(:visibility_level) { Snippet::PUBLIC }
let(:snippet) { create(:project_snippet, author: admin, visibility_level: visibility_level) }
it 'updates snippet' do
new_content = 'New content'
......@@ -133,6 +123,56 @@ describe API::ProjectSnippets, api: true do
expect(response).to have_http_status(400)
end
context 'when the snippet is spam' do
def update_snippet(snippet_params = {})
put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin), snippet_params
end
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the snippet is private' do
let(:visibility_level) { Snippet::PRIVATE }
it 'creates the snippet' do
expect { update_snippet(title: 'Foo') }.
to change { snippet.reload.title }.to('Foo')
end
end
context 'when the snippet is public' do
let(:visibility_level) { Snippet::PUBLIC }
it 'rejects the snippet' do
expect { update_snippet(title: 'Foo') }.
not_to change { snippet.reload.title }
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo') }.
to change { SpamLog.count }.by(1)
end
end
context 'when the private snippet is made public' do
let(:visibility_level) { Snippet::PRIVATE }
it 'rejects the snippet' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
not_to change { snippet.reload.title }
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end
describe 'DELETE /projects/:project_id/snippets/:id/' do
......
......@@ -122,7 +122,9 @@ describe API::Snippets, api: true do
it 'rejects the shippet' do
expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
end
it 'creates a spam log' do
......@@ -134,16 +136,20 @@ describe API::Snippets, api: true do
end
describe 'PUT /snippets/:id' do
let(:visibility_level) { Snippet::PUBLIC }
let(:other_user) { create(:user) }
let(:public_snippet) { create(:personal_snippet, :public, author: user) }
let(:snippet) do
create(:personal_snippet, author: user, visibility_level: visibility_level)
end
it 'updates snippet' do
new_content = 'New content'
put api("/snippets/#{public_snippet.id}", user), content: new_content
put api("/snippets/#{snippet.id}", user), content: new_content
expect(response).to have_http_status(200)
public_snippet.reload
expect(public_snippet.content).to eq(new_content)
snippet.reload
expect(snippet.content).to eq(new_content)
end
it 'returns 404 for invalid snippet id' do
......@@ -154,7 +160,7 @@ describe API::Snippets, api: true do
end
it "returns 404 for another user's snippet" do
put api("/snippets/#{public_snippet.id}", other_user), title: 'fubar'
put api("/snippets/#{snippet.id}", other_user), title: 'fubar'
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Snippet Not Found')
......@@ -165,6 +171,56 @@ describe API::Snippets, api: true do
expect(response).to have_http_status(400)
end
context 'when the snippet is spam' do
def update_snippet(snippet_params = {})
put api("/snippets/#{snippet.id}", user), snippet_params
end
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the snippet is private' do
let(:visibility_level) { Snippet::PRIVATE }
it 'updates the snippet' do
expect { update_snippet(title: 'Foo') }.
to change { snippet.reload.title }.to('Foo')
end
end
context 'when the snippet is public' do
let(:visibility_level) { Snippet::PUBLIC }
it 'rejects the shippet' do
expect { update_snippet(title: 'Foo') }.
not_to change { snippet.reload.title }
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo') }.
to change { SpamLog.count }.by(1)
end
end
context 'when a private snippet is made public' do
let(:visibility_level) { Snippet::PRIVATE }
it 'rejects the snippet' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
not_to change { snippet.reload.title }
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end
describe 'DELETE /snippets/:id' do
......
......@@ -986,6 +986,33 @@ describe API::V3::Issues, api: true do
end
end
describe 'PUT /projects/:id/issues/:issue_id with spam filtering' do
let(:params) do
{
title: 'updated title',
description: 'content here',
labels: 'label, label2'
}
end
it "does not create a new project issue" do
allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
allow_any_instance_of(AkismetService).to receive_messages(is_spam?: true)
put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), params
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
spam_logs = SpamLog.all
expect(spam_logs.count).to eq(1)
expect(spam_logs[0].title).to eq('updated title')
expect(spam_logs[0].description).to eq('content here')
expect(spam_logs[0].user).to eq(user)
expect(spam_logs[0].noteable_type).to eq('Issue')
end
end
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
let!(:label) { create(:label, title: 'dummy', project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) }
......
......@@ -85,43 +85,33 @@ describe API::ProjectSnippets, api: true do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the project is private' do
let(:private_project) { create(:project_empty_repo, :private) }
context 'when the snippet is public' do
it 'creates the snippet' do
expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
to change { Snippet.count }.by(1)
end
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
end
end
context 'when the project is public' do
context 'when the snippet is private' do
it 'creates the snippet' do
expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
to change { Snippet.count }.by(1)
end
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
end
context 'when the snippet is public' do
it 'rejects the shippet' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
not_to change { Snippet.count }
expect(response).to have_http_status(400)
end
it 'creates a spam log' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
it 'creates a spam log' do
expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end
describe 'PUT /projects/:project_id/snippets/:id/' do
let(:snippet) { create(:project_snippet, author: admin) }
let(:visibility_level) { Snippet::PUBLIC }
let(:snippet) { create(:project_snippet, author: admin, visibility_level: visibility_level) }
it 'updates snippet' do
new_content = 'New content'
......@@ -145,6 +135,56 @@ describe API::ProjectSnippets, api: true do
expect(response).to have_http_status(400)
end
context 'when the snippet is spam' do
def update_snippet(snippet_params = {})
put v3_api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin), snippet_params
end
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when the snippet is private' do
let(:visibility_level) { Snippet::PRIVATE }
it 'creates the snippet' do
expect { update_snippet(title: 'Foo') }.
to change { snippet.reload.title }.to('Foo')
end
end
context 'when the snippet is public' do
let(:visibility_level) { Snippet::PUBLIC }
it 'rejects the snippet' do
expect { update_snippet(title: 'Foo') }.
not_to change { snippet.reload.title }
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo') }.
to change { SpamLog.count }.by(1)
end
end
context 'when the private snippet is made public' do
let(:visibility_level) { Snippet::PRIVATE }
it 'rejects the snippet' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
not_to change { snippet.reload.title }
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
end
it 'creates a spam log' do
expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end
describe 'DELETE /projects/:project_id/snippets/:id/' do
......
......@@ -229,16 +229,6 @@ describe Issues::CreateService, services: true do
expect { issue }.not_to change{SpamLog.last.recaptcha_verified}
end
end
context 'when spam log title does not match the issue title' do
before do
opts[:title] = 'Another issue'
end
it 'does not mark spam_log as recaptcha_verified' do
expect { issue }.not_to change{SpamLog.last.recaptcha_verified}
end
end
end
context 'when recaptcha was not verified' do
......
require 'spec_helper'
describe SpamService, services: true do
describe '#check' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let(:request) { double(:request, env: {}) }
describe '#when_recaptcha_verified' do
def check_spam(issue, request, recaptcha_verified)
described_class.new(issue, request).when_recaptcha_verified(recaptcha_verified) do
'yielded'
end
end
it 'yields block when recaptcha was already verified' do
issue = build_stubbed(:issue)
def check_spam(issue, request)
described_class.new(issue, request).check
expect(check_spam(issue, nil, true)).to eql('yielded')
end
context 'when indicated as spam by akismet' do
before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: true)) }
context 'when recaptcha was not verified' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let(:request) { double(:request, env: {}) }
it 'returns false when request is missing' do
expect(check_spam(issue, nil)).to be_falsey
end
context 'when indicated as spam by akismet' do
before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: true)) }
it 'returns false when issue is not public' do
issue = create(:issue, project: create(:project, :private))
it 'doesnt check as spam when request is missing' do
check_spam(issue, nil, false)
expect(check_spam(issue, request)).to be_falsey
end
expect(issue.spam).to be_falsey
end
it 'returns true' do
expect(check_spam(issue, request)).to be_truthy
end
it 'checks as spam' do
check_spam(issue, request, false)
it 'creates a spam log' do
expect { check_spam(issue, request) }.to change { SpamLog.count }.from(0).to(1)
end
end
expect(issue.spam).to be_truthy
end
context 'when not indicated as spam by akismet' do
before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) }
it 'creates a spam log' do
expect { check_spam(issue, request, false) }
.to change { SpamLog.count }.from(0).to(1)
end
it 'returns false' do
expect(check_spam(issue, request)).to be_falsey
it 'doesnt yield block' do
expect(check_spam(issue, request, false))
.to eql(SpamLog.last)
end
end
it 'does not create a spam log' do
expect { check_spam(issue, request) }.not_to change { SpamLog.count }
context 'when not indicated as spam by akismet' do
before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) }
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
......
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