summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStan Hu <stanhu@gmail.com>2016-01-09 19:30:34 +0000
committerDouglas Barbosa Alexandre <dbalexandre@gmail.com>2016-02-02 11:25:44 -0200
commitd20e75a8d80c2828336cd22897ea6868d666f8a5 (patch)
tree9cbf725ad1b523ab0c8d94261d4a02a0677ca450
parent6cffcb05882b0d3c4a02f9acf21806e25ea09ec3 (diff)
downloadgitlab-ce-d20e75a8d80c2828336cd22897ea6868d666f8a5.tar.gz
Support Akismet spam checking for creation of issues via API
Currently any spam detected by Akismet by non-members via API will be logged in a separate table in the admin page. Closes #5612
-rw-r--r--CHANGELOG1
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock2
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/admin/spam_logs_controller.rb33
-rw-r--r--app/models/application_setting.rb8
-rw-r--r--app/models/spam_log.rb13
-rw-r--r--app/models/spam_report.rb5
-rw-r--r--app/models/user.rb1
-rw-r--r--app/services/create_spam_log_service.rb13
-rw-r--r--app/views/admin/application_settings/_form.html.haml23
-rw-r--r--app/views/admin/spam_logs/_spam_log.html.haml30
-rw-r--r--app/views/admin/spam_logs/index.html.haml21
-rw-r--r--app/views/layouts/nav/_admin.html.haml7
-rw-r--r--config/routes.rb2
-rw-r--r--db/migrate/20151231152326_add_akismet_to_application_settings.rb8
-rw-r--r--db/migrate/20160109054846_create_spam_logs.rb16
-rw-r--r--db/schema.rb15
-rw-r--r--doc/integration/README.md1
-rw-r--r--doc/integration/akismet.md30
-rw-r--r--doc/integration/img/akismet_settings.pngbin0 -> 55837 bytes
-rw-r--r--features/admin/spam_logs.feature8
-rw-r--r--features/steps/admin/spam_logs.rb18
-rw-r--r--features/steps/shared/paths.rb4
-rw-r--r--lib/api/issues.rb22
-rw-r--r--lib/gitlab/akismet_helper.rb39
-rw-r--r--lib/gitlab/current_settings.rb3
-rw-r--r--spec/controllers/admin/spam_logs_controller_spec.rb47
-rw-r--r--spec/factories/spam_logs.rb7
-rw-r--r--spec/lib/gitlab/akismet_helper_spec.rb35
-rw-r--r--spec/requests/api/issues_spec.rb22
31 files changed, 432 insertions, 7 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 7a70516173c..1be1f7374df 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -74,6 +74,7 @@ v 8.4.0
- Don't notify users twice if they are both project watchers and subscribers (Stan Hu)
- Remove gray background from layout in UI
- Fix signup for OAuth providers that don't provide a name
+ - Support Akismet spam checking for creation of issues via API (Stan Hu)
- Implement new UI for group page
- Implement search inside emoji picker
- Let the CI runner know about builds that this build depends on
diff --git a/Gemfile b/Gemfile
index c9d428a1798..9f012e1a7e5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -36,8 +36,9 @@ gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'rack-oauth2', '~> 1.2.1'
-# reCAPTCHA protection
+# Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails'
+gem 'akismet', '~> 2.0'
# Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index b4f7587c419..9570d387f68 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -49,6 +49,7 @@ GEM
addressable (2.3.8)
after_commit_queue (1.3.0)
activerecord (>= 3.0)
+ akismet (2.0.0)
allocations (1.0.3)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
@@ -884,6 +885,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
after_commit_queue
+ akismet (~> 2.0)
allocations (~> 1.0)
annotate (~> 2.6.0)
asana (~> 0.4.0)
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 9943745208e..1515086b16d 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -79,6 +79,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:recaptcha_private_key,
:sentry_enabled,
:sentry_dsn,
+ :akismet_enabled,
+ :akismet_api_key,
restricted_visibility_levels: [],
import_sources: []
)
diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
new file mode 100644
index 00000000000..69f94dd3ba8
--- /dev/null
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -0,0 +1,33 @@
+class Admin::SpamLogsController < Admin::ApplicationController
+ before_action :set_spam_log, only: [:destroy]
+
+ def index
+ @spam_logs = SpamLog.order(created_at: :desc).page(params[:page])
+ end
+
+ def destroy
+ @spam_log.destroy
+ message = 'Spam log was successfully destroyed.'
+
+ if params[:remove_user]
+ username = @spam_log.user.username
+ @spam_log.user.destroy
+ message = "User #{username} was successfully destroyed."
+ end
+
+ respond_to do |format|
+ format.json { render json: '{}' }
+ format.html { redirect_to admin_spam_logs_path, notice: message }
+ end
+ end
+
+ private
+
+ def set_spam_log
+ @spam_log = SpamLog.find(params[:id])
+ end
+
+ def spam_log_params
+ params[:spam_log]
+ end
+end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 59563b8823c..9cafc78f761 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -88,6 +88,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :sentry_enabled
+ validates :akismet_api_key,
+ presence: true,
+ if: :akismet_enabled
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
@@ -143,7 +147,9 @@ class ApplicationSetting < ActiveRecord::Base
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
- two_factor_grace_period: 48
+ two_factor_grace_period: 48,
+ recaptcha_enabled: false,
+ akismet_enabled: false
)
end
diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb
new file mode 100644
index 00000000000..132c5713e0a
--- /dev/null
+++ b/app/models/spam_log.rb
@@ -0,0 +1,13 @@
+class SpamLog < ActiveRecord::Base
+ belongs_to :user
+
+ validates :user, presence: true
+
+ def truncated_description
+ if description.present? && description.length > 100
+ return description[0..100] + "..."
+ end
+
+ description
+ end
+end
diff --git a/app/models/spam_report.rb b/app/models/spam_report.rb
new file mode 100644
index 00000000000..cdc7321b08e
--- /dev/null
+++ b/app/models/spam_report.rb
@@ -0,0 +1,5 @@
+class SpamReport < ActiveRecord::Base
+ belongs_to :user
+
+ validates :user, presence: true
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 4214f01f6a4..63c895683f7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -138,6 +138,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
has_one :abuse_report, dependent: :destroy
+ has_many :spam_log, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
diff --git a/app/services/create_spam_log_service.rb b/app/services/create_spam_log_service.rb
new file mode 100644
index 00000000000..59a66fde47a
--- /dev/null
+++ b/app/services/create_spam_log_service.rb
@@ -0,0 +1,13 @@
+class CreateSpamLogService < BaseService
+ def initialize(project, user, params)
+ super(project, user, params)
+ end
+
+ def execute
+ spam_params = params.merge({ user_id: @current_user.id,
+ project_id: @project.id } )
+ spam_log = SpamLog.new(spam_params)
+ spam_log.save
+ spam_log
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index b0f1a34cbec..b4e3d96d405 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -218,20 +218,37 @@
= f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled
Enable reCAPTCHA
- %span.help-block#recaptcha_help_block Helps preventing bots from creating accounts
+ %span.help-block#recaptcha_help_block Helps prevent bots from creating accounts
.form-group
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :recaptcha_site_key, class: 'form-control'
.help-block
- Generate site and private keys here:
- %a{ href: 'http://www.google.com/recaptcha', target: '_blank'} http://www.google.com/recaptcha
+ Generate site and private keys at
+ %a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
+
.form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :recaptcha_private_key, class: 'form-control'
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :akismet_enabled do
+ = f.check_box :akismet_enabled
+ Enable Akismet
+ %span.help-block#akismet_help_block Helps prevent bots from creating issues
+
+ .form-group
+ = f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :akismet_api_key, class: 'form-control'
+ .help-block
+ Generate API key at
+ %a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com
+
%fieldset
%legend Error Reporting and Logging
%p
diff --git a/app/views/admin/spam_logs/_spam_log.html.haml b/app/views/admin/spam_logs/_spam_log.html.haml
new file mode 100644
index 00000000000..1c793bbf738
--- /dev/null
+++ b/app/views/admin/spam_logs/_spam_log.html.haml
@@ -0,0 +1,30 @@
+- user = spam_log.user
+%tr
+ %td
+ = spam_log.created_at.to_s(:short)
+ %td
+ - if user
+ = link_to user.name, user
+ - else
+ (removed)
+ %td
+ = spam_log.source_ip
+ %td
+ = spam_log.via_api ? 'Y' : 'N'
+ %td
+ = spam_log.noteable_type
+ %td
+ = spam_log.title
+ %td
+ = spam_log.truncated_description
+ %td
+ - if user
+ = link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
+ data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
+ %td
+ - if user && !user.blocked?
+ = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
+ - else
+ .btn.btn-xs.disabled
+ Already Blocked
+ = link_to 'Remove log', [:admin, spam_log, format: :json], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
diff --git a/app/views/admin/spam_logs/index.html.haml b/app/views/admin/spam_logs/index.html.haml
new file mode 100644
index 00000000000..439468e572b
--- /dev/null
+++ b/app/views/admin/spam_logs/index.html.haml
@@ -0,0 +1,21 @@
+- page_title "Spam Logs"
+%h3.page-title Spam Logs
+%hr
+- if @spam_logs.present?
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Date
+ %th User
+ %th Source IP
+ %th Via API?
+ %th Type
+ %th Title
+ %th Description
+ %th Primary Action
+ %th
+ = render @spam_logs
+ = paginate @spam_logs
+- else
+ %h4 There are no Spam Logs
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index cffdb52cc23..8dc20587421 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -82,6 +82,13 @@
Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all))
+ = nav_link(controller: :spam_logs) do
+ = link_to admin_spam_logs_path, title: "Spam Logs" do
+ = icon('exclamation-triangle fw')
+ %span
+ Spam Logs
+ %span.count= number_with_delimiter(SpamLog.count(:all))
+
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
diff --git a/config/routes.rb b/config/routes.rb
index 54cc338b605..034bfaf1bcd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -211,6 +211,8 @@ Rails.application.routes.draw do
end
resources :abuse_reports, only: [:index, :destroy]
+ resources :spam_logs, only: [:index, :destroy]
+
resources :applications
resources :groups, constraints: { id: /[^\/]+/ } do
diff --git a/db/migrate/20151231152326_add_akismet_to_application_settings.rb b/db/migrate/20151231152326_add_akismet_to_application_settings.rb
new file mode 100644
index 00000000000..3f52c758f9a
--- /dev/null
+++ b/db/migrate/20151231152326_add_akismet_to_application_settings.rb
@@ -0,0 +1,8 @@
+class AddAkismetToApplicationSettings < ActiveRecord::Migration
+ def change
+ change_table :application_settings do |t|
+ t.boolean :akismet_enabled, default: false
+ t.string :akismet_api_key
+ end
+ end
+end
diff --git a/db/migrate/20160109054846_create_spam_logs.rb b/db/migrate/20160109054846_create_spam_logs.rb
new file mode 100644
index 00000000000..f12fe9f8f78
--- /dev/null
+++ b/db/migrate/20160109054846_create_spam_logs.rb
@@ -0,0 +1,16 @@
+class CreateSpamLogs < ActiveRecord::Migration
+ def change
+ create_table :spam_logs do |t|
+ t.integer :user_id
+ t.string :source_ip
+ t.string :user_agent
+ t.boolean :via_api
+ t.integer :project_id
+ t.string :noteable_type
+ t.string :title
+ t.text :description
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2ad2c23fba5..d546e06cd8a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -64,6 +64,8 @@ ActiveRecord::Schema.define(version: 20160128233227) do
t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false
t.string "sentry_dsn"
+ t.boolean "akismet_enabled", default: false
+ t.string "akismet_api_key"
end
create_table "audit_events", force: :cascade do |t|
@@ -770,6 +772,19 @@ ActiveRecord::Schema.define(version: 20160128233227) do
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
+ create_table "spam_logs", force: :cascade do |t|
+ t.integer "user_id"
+ t.string "source_ip"
+ t.string "user_agent"
+ t.boolean "via_api"
+ t.integer "project_id"
+ t.string "noteable_type"
+ t.string "title"
+ t.text "description"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
create_table "subscriptions", force: :cascade do |t|
t.integer "user_id"
t.integer "subscribable_id"
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 83116bc8370..281eea8363d 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -15,6 +15,7 @@ See the documentation below for details on how to configure these services.
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
+- [Akismet](akismet.md) Configure Akismet to stop spam
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md
new file mode 100644
index 00000000000..5cc09bd536d
--- /dev/null
+++ b/doc/integration/akismet.md
@@ -0,0 +1,30 @@
+# Akismet
+
+GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently
+GitLab uses Akismet to prevent users who are not members of a project from
+creating spam via the GitLab API. Detected spam will be rejected, and
+an entry in the "Spam Log" section in the Admin page will be created.
+
+Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that
+adding a user to a project will disable the Akismet check and prevent this
+from happening.
+
+## Configuration
+
+To use Akismet:
+
+1. Go to the URL: https://akismet.com/account/
+
+2. Sign-in or create a new account.
+
+3. Click on "Show" to reveal the API key.
+
+4. Go to Applications Settings on Admin Area (`admin/application_settings`)
+
+5. Check the `Enable Akismet` checkbox
+
+6. Fill in the API key from step 3.
+
+7. Save the configuration.
+
+![Screenshot of Akismet settings](img/akismet_settings.png)
diff --git a/doc/integration/img/akismet_settings.png b/doc/integration/img/akismet_settings.png
new file mode 100644
index 00000000000..ccdd3adb1c5
--- /dev/null
+++ b/doc/integration/img/akismet_settings.png
Binary files differ
diff --git a/features/admin/spam_logs.feature b/features/admin/spam_logs.feature
new file mode 100644
index 00000000000..92a5389e3a4
--- /dev/null
+++ b/features/admin/spam_logs.feature
@@ -0,0 +1,8 @@
+Feature: Admin spam logs
+ Background:
+ Given I sign in as an admin
+ And spam logs exist
+
+ Scenario: Browse spam logs
+ When I visit spam logs page
+ Then I should see list of spam logs
diff --git a/features/steps/admin/spam_logs.rb b/features/steps/admin/spam_logs.rb
new file mode 100644
index 00000000000..79d18b036b0
--- /dev/null
+++ b/features/steps/admin/spam_logs.rb
@@ -0,0 +1,18 @@
+class Spinach::Features::AdminSpamLogs < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+
+ step 'I should see list of spam logs' do
+ page.should have_content("Spam Logs")
+ spam_log = SpamLog.first
+ page.should have_content spam_log.title
+ page.should have_content spam_log.description
+ page.should have_link("Remove user")
+ page.should have_link("Block user")
+ end
+
+ step 'spam logs exist' do
+ create(:spam_log)
+ end
+end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 4264c9c6f1a..2c854ac7bf9 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -191,6 +191,10 @@ module SharedPaths
visit admin_application_settings_path
end
+ step 'I visit spam logs page' do
+ visit admin_spam_logs_path
+ end
+
step 'I visit applications page' do
visit admin_applications_path
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 6e7a7672070..cdadd13c13a 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -3,6 +3,8 @@ module API
class Issues < Grape::API
before { authenticate! }
+ helpers ::Gitlab::AkismetHelper
+
helpers do
def filter_issues_state(issues, state)
case state
@@ -19,6 +21,15 @@ module API
def filter_issues_milestone(issues, milestone)
issues.includes(:milestone).where('milestones.title' => milestone)
end
+
+ def create_spam_log(project, current_user, attrs)
+ params = attrs.dup
+ params[:source_ip] = env['REMOTE_ADDR']
+ params[:user_agent] = env['HTTP_USER_AGENT']
+ params[:noteable_type] = 'Issue'
+ params[:via_api] = true
+ ::CreateSpamLogService.new(project, current_user, params).execute
+ end
end
resource :issues do
@@ -114,7 +125,16 @@ module API
render_api_error!({ labels: errors }, 400)
end
- issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
+ project = user_project
+ text = attrs[:title]
+ text += "\n#{attrs[:description]}" if attrs[:description].present?
+
+ if check_for_spam?(project, current_user) && is_spam?(env, current_user, text)
+ create_spam_log(project, current_user, attrs)
+ render_api_error!({ error: 'Spam detected' }, 400)
+ end
+
+ issue = ::Issues::CreateService.new(project, current_user, attrs).execute
if issue.valid?
# Find or create labels and attach to issue. Labels are valid because
diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb
new file mode 100644
index 00000000000..71f525309fe
--- /dev/null
+++ b/lib/gitlab/akismet_helper.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module AkismetHelper
+ def akismet_enabled?
+ current_application_settings.akismet_enabled
+ end
+
+ def akismet_client
+ ::Akismet::Client.new(current_application_settings.akismet_api_key,
+ Gitlab.config.gitlab.url)
+ end
+
+ def check_for_spam?(project, user)
+ akismet_enabled? && !project.team.member?(user)
+ end
+
+ def is_spam?(environment, user, text)
+ client = akismet_client
+ ip_address = environment['REMOTE_ADDR']
+ user_agent = environment['HTTP_USER_AGENT']
+
+ params = {
+ type: 'comment',
+ text: text,
+ created_at: DateTime.now,
+ author: user.name,
+ author_email: user.email,
+ referrer: environment['HTTP_REFERER'],
+ }
+
+ begin
+ is_spam, is_blatant = client.check(ip_address, user_agent, params)
+ is_spam || is_blatant
+ rescue => e
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index a6b2f14521c..8531c7e87e1 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -34,7 +34,8 @@ module Gitlab
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
- two_factor_grace_period: 48
+ two_factor_grace_period: 48,
+ akismet_enabled: false
)
end
diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb
new file mode 100644
index 00000000000..2486298fc78
--- /dev/null
+++ b/spec/controllers/admin/spam_logs_controller_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Admin::SpamLogsController do
+ let(:admin) { create(:admin) }
+ let(:spam_log) { create(:spam_log, user: admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe '#index' do
+ it 'lists all spam logs' do
+ get :index
+ expect(response.status).to eq(200)
+ end
+ end
+
+ describe '#destroy' do
+ it 'destroys just spam log' do
+ user = spam_log.user
+ delete :destroy, id: spam_log.id
+
+ expect(SpamLog.all.count).to eq(0)
+ expect(User.find(user.id)).to be_truthy
+ expect(response.status).to eq(302)
+ end
+
+ it 'destroys user and his spam logs' do
+ user = spam_log.user
+ delete :destroy, id: spam_log.id, remove_user: true
+
+ expect(SpamLog.all.count).to eq(0)
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect(response.status).to eq(302)
+ end
+
+ it 'destroys user and his spam logs with JSON format' do
+ user = spam_log.user
+ delete :destroy, id: spam_log.id, remove_user: true, format: :json
+
+ expect(SpamLog.all.count).to eq(0)
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect(JSON.parse(response.body)).to eq({})
+ expect(response.status).to eq(200)
+ end
+ end
+end
diff --git a/spec/factories/spam_logs.rb b/spec/factories/spam_logs.rb
new file mode 100644
index 00000000000..9e8686d73c2
--- /dev/null
+++ b/spec/factories/spam_logs.rb
@@ -0,0 +1,7 @@
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :spam_log do
+ user
+ end
+end
diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb
new file mode 100644
index 00000000000..9858935180a
--- /dev/null
+++ b/spec/lib/gitlab/akismet_helper_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Gitlab::AkismetHelper, type: :helper do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
+ current_application_settings.akismet_enabled = true
+ current_application_settings.akismet_api_key = '12345'
+ end
+
+ describe '#check_for_spam?' do
+ it 'returns true for non-member' do
+ expect(helper.check_for_spam?(project, user)).to eq(true)
+ end
+
+ it 'returns false for member' do
+ project.team << [user, :guest]
+ expect(helper.check_for_spam?(project, user)).to eq(false)
+ end
+ end
+
+ describe '#is_spam?' do
+ it 'returns true for spam' do
+ environment = {
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'HTTP_USER_AGENT' => 'Test User Agent'
+ }
+
+ allow_any_instance_of(::Akismet::Client).to receive(:check).and_return([true, true])
+ expect(helper.is_spam?(environment, user, 'Is this spam?')).to eq(true)
+ end
+ end
+end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 5e65ad18c0e..2e50344c149 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -241,6 +241,28 @@ describe API::API, api: true do
end
end
+ describe 'POST /projects/:id/issues with spam filtering' do
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:check_for_spam?).and_return(true)
+ allow(endpoint).to receive(:is_spam?).and_return(true)
+ end
+ end
+
+ it "should create a new project issue" do
+ post api("/projects/#{project.id}/issues", user),
+ title: 'new issue', labels: 'label, label2'
+ expect(response.status).to eq(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('new issue')
+ expect(spam_logs[0].user).to eq(user)
+ expect(spam_logs[0].noteable_type).to eq('Issue')
+ expect(spam_logs[0].project_id).to eq(project.id)
+ end
+ end
+
describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),