Commit 96399a81 authored by Patricio Cano's avatar Patricio Cano

Allow `Issue` to be submitted as spam

- Added controller actions as reusable concerns
- Added controller tests
parent abf2dcd2
...@@ -164,6 +164,10 @@ ...@@ -164,6 +164,10 @@
@include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light); @include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light);
} }
&.btn-spam {
@include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light);
}
&.btn-danger, &.btn-danger,
&.btn-remove, &.btn-remove,
&.btn-red { &.btn-red {
......
module SpammableActions
extend ActiveSupport::Concern
included do
before_action :authorize_submit_spammable!, only: :mark_as_spam
end
def mark_as_spam
if spammable.submit_spam
spammable.user_agent_detail.update_attribute(:submitted, true)
if spammable.is_a?(Issuable)
SystemNoteService.submit_spam(spammable, spammable.project, current_user)
end
redirect_to spammable, notice: 'Issue was submitted to Akismet successfully.'
else
flash[:error] = 'Error with Akismet. Please check the logs for more info.'
redirect_to spammable
end
end
private
def spammable
raise NotImplementedError
end
def authorize_submit_spammable!
access_denied! unless current_user.admin?
end
end
...@@ -4,6 +4,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -4,6 +4,7 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions include IssuableActions
include ToggleAwardEmoji include ToggleAwardEmoji
include IssuableCollections include IssuableCollections
include SpammableActions
before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :module_enabled before_action :module_enabled
...@@ -185,6 +186,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -185,6 +186,7 @@ class Projects::IssuesController < Projects::ApplicationController
alias_method :subscribable_resource, :issue alias_method :subscribable_resource, :issue
alias_method :issuable, :issue alias_method :issuable, :issue
alias_method :awardable, :issue alias_method :awardable, :issue
alias_method :spammable, :issue
def authorize_read_issue! def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue) return render_404 unless can?(current_user, :read_issue, @issue)
......
...@@ -22,7 +22,7 @@ module Spammable ...@@ -22,7 +22,7 @@ module Spammable
def can_be_submitted? def can_be_submitted?
if user_agent_detail if user_agent_detail
user_agent_detail.submittable? user_agent_detail.submittable? && akismet_enabled?
else else
false false
end end
...@@ -41,6 +41,14 @@ module Spammable ...@@ -41,6 +41,14 @@ module Spammable
@spam @spam
end end
def submitted?
if user_agent_detail
user_agent_detail.submitted
else
false
end
end
def check_for_spam def check_for_spam
self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam? self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
end end
...@@ -53,17 +61,21 @@ module Spammable ...@@ -53,17 +61,21 @@ module Spammable
end end
end end
def to_ability_name
self.class.to_s.underscore
end
# Override this method if an additional check is needed before calling Akismet # Override this method if an additional check is needed before calling Akismet
def check_for_spam? def check_for_spam?
akismet_enabled? akismet_enabled?
end end
def spam_title def spam_title
raise 'Implement in included model!' raise NotImplementedError
end end
def spam_description def spam_description
raise 'Implement in included model!' raise NotImplementedError
end end
private private
......
...@@ -395,6 +395,23 @@ module SystemNoteService ...@@ -395,6 +395,23 @@ module SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body) create_note(noteable: noteable, project: project, author: author, note: body)
end end
# Called when the status of a Issuable is submitted as spam
#
# noteable - Noteable object
# project - Project owning noteable
# author - User performing the change
#
# Example Note text:
#
# "Issue submitted as spam."
#
# Returns the created Note object
def submit_spam(noteable, project, author)
body = "Submitted #{noteable.class.to_s.downcase} as spam"
create_note(noteable: noteable, project: project, author: author, note: body)
end
private private
def notes_for_mentioner(mentioner, noteable, notes) def notes_for_mentioner(mentioner, noteable, notes)
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
%td %td
- if spam_log.submitted_as_ham? - if spam_log.submitted_as_ham?
.btn.btn-xs.disabled .btn.btn-xs.disabled
Submitted as Ham Submitted as ham
- else - else
= link_to 'Submit as ham', mark_as_ham_admin_spam_log_path(spam_log), method: :post, class: 'btn btn-xs btn-warning' = link_to 'Submit as ham', mark_as_ham_admin_spam_log_path(spam_log), method: :post, class: 'btn btn-xs btn-warning'
- if user && !user.blocked? - if user && !user.blocked?
......
...@@ -37,14 +37,21 @@ ...@@ -37,14 +37,21 @@
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li %li
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
- if @issue.can_be_submitted? && current_user.admin?
- unless @issue.submitted?
%li
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
- if can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue New issue
- if can?(current_user, :update_issue, @issue) - if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' do = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
Edit - if @issue.can_be_submitted? && current_user.admin?
- unless @issue.submitted?
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
.issue-details.issuable-details .issue-details.issuable-details
......
...@@ -817,6 +817,7 @@ Rails.application.routes.draw do ...@@ -817,6 +817,7 @@ Rails.application.routes.draw do
member do member do
post :toggle_subscription post :toggle_subscription
post :toggle_award_emoji post :toggle_award_emoji
post :mark_as_spam
get :referenced_merge_requests get :referenced_merge_requests
get :related_branches get :related_branches
get :can_create_branch get :can_create_branch
......
...@@ -5,6 +5,7 @@ class CreateUserAgentDetails < ActiveRecord::Migration ...@@ -5,6 +5,7 @@ class CreateUserAgentDetails < ActiveRecord::Migration
t.string :ip_address, null: false t.string :ip_address, null: false
t.integer :subject_id, null: false t.integer :subject_id, null: false
t.string :subject_type, null: false t.string :subject_type, null: false
t.boolean :submitted, default: false
t.timestamps null: false t.timestamps null: false
end end
......
...@@ -1004,6 +1004,7 @@ ActiveRecord::Schema.define(version: 20160810142633) do ...@@ -1004,6 +1004,7 @@ ActiveRecord::Schema.define(version: 20160810142633) do
t.string "ip_address", null: false t.string "ip_address", null: false
t.integer "subject_id", null: false t.integer "subject_id", null: false
t.string "subject_type", null: false t.string "subject_type", null: false
t.boolean "submitted", default: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
......
...@@ -34,4 +34,16 @@ describe Admin::SpamLogsController do ...@@ -34,4 +34,16 @@ describe Admin::SpamLogsController do
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
end end
end end
describe '#mark_as_ham' do
before do
allow_any_instance_of(Gitlab::AkismetHelper).to receive(:ham!).and_return(true)
end
it 'submits the log as ham' do
post :mark_as_ham, id: first_spam.id
expect(response).to have_http_status(302)
expect(SpamLog.find(first_spam.id).submitted_as_ham).to be_truthy
end
end
end end
...@@ -322,6 +322,35 @@ describe Projects::IssuesController do ...@@ -322,6 +322,35 @@ describe Projects::IssuesController do
end end
end end
describe 'POST #mark_as_spam' do
context 'properly submits to Akismet' do
before do
allow_any_instance_of(Spammable).to receive_messages(can_be_submitted?: true, submit_spam: true)
end
def post_spam
admin = create(:admin)
create(:user_agent_detail, subject: issue)
project.team << [admin, :master]
sign_in(admin)
post :mark_as_spam, {
namespace_id: project.namespace.path,
project_id: project.path,
id: issue.iid
}
end
it 'creates a system note' do
expect{ post_spam }.to change(Note, :count)
end
it 'updates issue' do
post_spam
expect(issue.submitted?).to be_truthy
end
end
end
describe "DELETE #destroy" do describe "DELETE #destroy" do
context "when the user is a developer" do context "when the user is a developer" do
before { sign_in(user) } before { sign_in(user) }
......
...@@ -14,6 +14,10 @@ describe Issue, 'Spammable' do ...@@ -14,6 +14,10 @@ describe Issue, 'Spammable' do
end end
describe 'InstanceMethods' do describe 'InstanceMethods' do
before do
allow_any_instance_of(Gitlab::AkismetHelper).to receive(:akismet_enabled?).and_return(true)
end
it 'should return the correct creator' do it 'should return the correct creator' do
expect(issue.send(:owner).id).to eq(issue.author_id) expect(issue.send(:owner).id).to eq(issue.author_id)
end end
...@@ -24,14 +28,11 @@ describe Issue, 'Spammable' do ...@@ -24,14 +28,11 @@ describe Issue, 'Spammable' do
end end
it 'should be submittable' do it 'should be submittable' do
create(:user_agent_detail, subject_id: issue.id, subject_type: issue.class.to_s) create(:user_agent_detail, subject: issue)
expect(issue.can_be_submitted?).to be_truthy expect(issue.can_be_submitted?).to be_truthy
end end
describe '#check_for_spam?' do describe '#check_for_spam?' do
before do
allow_any_instance_of(Gitlab::AkismetHelper).to receive(:akismet_enabled?).and_return(true)
end
it 'returns true for public project' do it 'returns true for public project' do
issue.project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) issue.project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
expect(issue.check_for_spam?).to eq(true) expect(issue.check_for_spam?).to eq(true)
......
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