Commit c63194ce authored by Sean McGivern's avatar Sean McGivern
Browse files

Check public snippets for spam

Apply the same spam checks to public snippets (either personal snippets
that are public, or public snippets on public projects) as to issues on
public projects.
parent a9bc1c1d53ff
......@@ -7,7 +7,7 @@ module SpammableActions
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully."
redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
else
redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
end
......
class Projects::SnippetsController < Projects::ApplicationController
include ToggleAwardEmoji
include SpammableActions
before_action :module_enabled
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji]
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
# Allow read any snippet
before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
......@@ -36,8 +37,8 @@ def new
end
def create
@snippet = CreateSnippetService.new(@project, current_user,
snippet_params).execute
create_params = snippet_params.merge(request: request)
@snippet = CreateSnippetService.new(@project, current_user, create_params).execute
if @snippet.valid?
respond_with(@snippet,
......@@ -88,6 +89,7 @@ def snippet
@snippet ||= @project.snippets.find(params[:id])
end
alias_method :awardable, :snippet
alias_method :spammable, :snippet
def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
......
class SnippetsController < ApplicationController
include ToggleAwardEmoji
include SpammableActions
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
......@@ -40,8 +41,8 @@ def new
end
def create
@snippet = CreateSnippetService.new(nil, current_user,
snippet_params).execute
create_params = snippet_params.merge(request: request)
@snippet = CreateSnippetService.new(nil, current_user, create_params).execute
respond_with @snippet.becomes(Snippet)
end
......@@ -96,6 +97,7 @@ def snippet
end
end
alias_method :awardable, :snippet
alias_method :spammable, :snippet
def authorize_read_snippet!
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
......
......@@ -34,7 +34,13 @@ def spam?
end
def check_for_spam
self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
if spam?
self.errors.add(:base, "Your #{spammable_entity_type} has been recognized as spam and has been discarded.")
end
end
def spammable_entity_type
self.class.name.underscore
end
def spam_title
......
......@@ -9,4 +9,8 @@ class ProjectSnippet < Snippet
participant :author
participant :notes_with_associations
def check_for_spam?
super && project.public?
end
end
......@@ -7,6 +7,7 @@ class Snippet < ActiveRecord::Base
include Sortable
include Awardable
include Mentionable
include Spammable
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :content
......@@ -46,6 +47,9 @@ def content_html_invalidated?
participant :author
participant :notes_with_associations
attr_spammable :title, spam_title: true
attr_spammable :content, spam_description: true
def self.reference_prefix
'$'
end
......@@ -127,6 +131,14 @@ def notes_with_associations
notes.includes(:author)
end
def check_for_spam?
public?
end
def spammable_entity_type
'snippet'
end
class << self
# Searches for snippets with a matching title or file name.
#
......
class CreateSnippetService < BaseService
def execute
request = params.delete(:request)
api = params.delete(:api)
snippet = if project
project.snippets.build(params)
else
......@@ -12,8 +15,12 @@ def execute
end
snippet.author = current_user
snippet.spam = SpamService.new(snippet, request).check(api)
if snippet.save
UserAgentDetailService.new(snippet, request).create
end
snippet.save
snippet
end
end
......@@ -8,6 +8,8 @@
- if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
New snippet
- if @snippet.submittable_as_spam? && current_user.admin?
= link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
......@@ -27,3 +29,6 @@
%li
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
Edit
- if @snippet.submittable_as_spam? && current_user.admin?
%li
= link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post
......@@ -8,6 +8,8 @@
- if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
New snippet
- if @snippet.submittable_as_spam? && current_user.admin?
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if current_user
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
......@@ -26,3 +28,6 @@
%li
= link_to edit_snippet_path(@snippet) do
Edit
- if @snippet.submittable_as_spam? && current_user.admin?
%li
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
---
title: Check public snippets for spam
merge_request:
author:
......@@ -64,6 +64,7 @@
resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
member do
get 'raw'
post :mark_as_spam
end
end
......
......@@ -2,6 +2,7 @@
member do
get 'raw'
get 'download'
post :mark_as_spam
end
end
......
......@@ -58,7 +58,7 @@ def snippets_for_current_user
end
post ":id/snippets" do
authorize! :create_project_snippet, user_project
snippet_params = declared_params
snippet_params = declared_params.merge(request: request, api: true)
snippet_params[:content] = snippet_params.delete(:code)
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
......
......@@ -64,7 +64,7 @@ def public_snippets
desc: 'The visibility level of the snippet'
end
post do
attrs = declared_params(include_missing: false)
attrs = declared_params(include_missing: false).merge(request: request, api: true)
snippet = CreateSnippetService.new(nil, current_user, attrs).execute
if snippet.persisted?
......
......@@ -69,6 +69,86 @@
end
end
describe 'POST #create' do
def create_snippet(project, snippet_params = {})
sign_in(user)
project.team << [user, :developer]
post :create, {
namespace_id: project.namespace.to_param,
project_id: project.to_param,
project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}
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 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
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
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
end
end
end
end
describe 'POST #mark_as_spam' do
let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
before do
allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
stub_application_setting(akismet_enabled: true)
end
def mark_as_spam
admin = create(:admin)
create(:user_agent_detail, subject: snippet)
project.team << [admin, :master]
sign_in(admin)
post :mark_as_spam,
namespace_id: project.namespace.path,
project_id: project.path,
id: snippet.id
end
it 'updates the snippet' do
mark_as_spam
expect(snippet.reload).not_to be_submittable_as_spam
end
end
%w[show raw].each do |action|
describe "GET ##{action}" do
context 'when the project snippet is private' do
......
......@@ -138,6 +138,65 @@
end
end
describe 'POST #create' do
def create_snippet(snippet_params = {})
sign_in(user)
post :create, {
personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}
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
it 'creates the snippet' do
expect { create_snippet(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(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
end
end
end
describe 'POST #mark_as_spam' do
let(:snippet) { create(:personal_snippet, :public, author: user) }
before do
allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
stub_application_setting(akismet_enabled: true)
end
def mark_as_spam
admin = create(:admin)
create(:user_agent_detail, subject: snippet)
sign_in(admin)
post :mark_as_spam, id: snippet.id
end
it 'updates the snippet' do
mark_as_spam
expect(snippet.reload).not_to be_submittable_as_spam
end
end
%w(raw download).each do |action|
describe "GET #{action}" do
context 'when the personal snippet is private' do
......
......@@ -52,6 +52,7 @@ snippets:
- project
- notes
- award_emoji
- user_agent_detail
releases:
- project
project_members:
......
......@@ -4,6 +4,7 @@
include ApiHelpers
let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) }
let(:admin) { create(:admin) }
describe 'GET /projects/:project_id/snippets/:id' do
......@@ -50,7 +51,7 @@
title: 'Test Title',
file_name: 'test.rb',
code: 'puts "hello world"',
visibility_level: Gitlab::VisibilityLevel::PUBLIC
visibility_level: Snippet::PUBLIC
}
end
......@@ -72,6 +73,51 @@
expect(response).to have_http_status(400)
end
context 'when the snippet is spam' do
def create_snippet(project, snippet_params = {})
project.team << [user, :developer]
post api("/projects/#{project.id}/snippets", user), params.merge(snippet_params)
end
before 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
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
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
end
end
end
end
describe 'PUT /projects/:project_id/snippets/:id/' do
......
......@@ -80,7 +80,7 @@
title: 'Test Title',
file_name: 'test.rb',
content: 'puts "hello world"',
visibility_level: Gitlab::VisibilityLevel::PUBLIC
visibility_level: Snippet::PUBLIC
}
end
......@@ -101,6 +101,36 @@
expect(response).to have_http_status(400)
end
context 'when the snippet is spam' do
def create_snippet(snippet_params = {})
post api('/snippets', user), params.merge(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
it 'creates the snippet' do
expect { create_snippet(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(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(visibility_level: Snippet::PUBLIC) }.
to change { SpamLog.count }.by(1)
end
end
end
end
describe 'PUT /snippets/:id' do
......
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