Commit d20e75a8 authored by Stan Hu's avatar Stan Hu Committed by Douglas Barbosa Alexandre
Browse files

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
parent c7c21e4aa3f1
......@@ -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
......@@ -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'
......@@ -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)
akismet (~> 2.0)
allocations (~> 1.0)
annotate (~> 2.6.0)
asana (~> 0.4.0)
......@@ -79,6 +79,8 @@ def application_setting_params
restricted_visibility_levels: [],
import_sources: []
class Admin::SpamLogsController < Admin::ApplicationController
before_action :set_spam_log, only: [:destroy]
def index
@spam_logs = SpamLog.order(created_at: :desc).page(params[:page])
def destroy
message = 'Spam log was successfully destroyed.'
if params[:remove_user]
username = @spam_log.user.username
message = "User #{username} was successfully destroyed."
respond_to do |format|
format.json { render json: '{}' }
format.html { redirect_to admin_spam_logs_path, notice: message }
def set_spam_log
@spam_log = SpamLog.find(params[:id])
def spam_log_params
......@@ -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 @@ def self.create_from_defaults
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
class SpamLog < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
def truncated_description
if description.present? && description.length > 100
return description[0..100] + "..."
class SpamReport < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
......@@ -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'
class CreateSpamLogService < BaseService
def initialize(project, user, params)
super(project, user, params)
def execute
spam_params = params.merge({ user_id:,
project_id: } )
spam_log =
......@@ -218,20 +218,37 @@
= f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled
Enable reCAPTCHA Helps preventing bots from creating accounts Helps prevent bots from creating accounts
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
= f.text_field :recaptcha_site_key, class: 'form-control'
Generate site and private keys here:
%a{ href: '', target: '_blank'}
Generate site and private keys at
%a{ href: '', target: 'blank'}
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
= f.text_field :recaptcha_private_key, class: 'form-control'
= f.label :akismet_enabled do
= f.check_box :akismet_enabled
Enable Akismet Helps prevent bots from creating issues
= f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2'
= f.text_field :akismet_api_key, class: 'form-control'
Generate API key at
%a{ href: '', target: 'blank'}
%legend Error Reporting and Logging
- user = spam_log.user
= spam_log.created_at.to_s(:short)
- if user
= link_to, user
- else
= spam_log.source_ip
= spam_log.via_api ? 'Y' : 'N'
= spam_log.noteable_type
= spam_log.title
= spam_log.truncated_description
- if user
= link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
data: { confirm: "USER #{} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
- 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
Already Blocked
= link_to 'Remove log', [:admin, spam_log, format: :json], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
- page_title "Spam Logs" Spam Logs
- if @spam_logs.present?
%th Date
%th User
%th Source IP
%th Via API?
%th Type
%th Title
%th Description
%th Primary Action
= render @spam_logs
= paginate @spam_logs
- else
%h4 There are no Spam Logs
......@@ -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')
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')
......@@ -211,6 +211,8 @@
resources :abuse_reports, only: [:index, :destroy]
resources :spam_logs, only: [:index, :destroy]
resources :applications
resources :groups, constraints: { id: /[^\/]+/ } do
class AddAkismetToApplicationSettings < ActiveRecord::Migration
def change
change_table :application_settings do |t|
t.boolean :akismet_enabled, default: false
t.string :akismet_api_key
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
......@@ -64,6 +64,8 @@
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"
create_table "audit_events", force: :cascade do |t|
......@@ -770,6 +772,19 @@
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
create_table "subscriptions", force: :cascade do |t|
t.integer "user_id"
t.integer "subscribable_id"
......@@ -15,6 +15,7 @@ See the documentation below for details on how to configure these services.
- [OAuth2 provider]( OAuth2 application creation
- [Gmail actions buttons]( Adds GitLab actions to messages
- [reCAPTCHA]( Configure GitLab to use Google reCAPTCHA for new users
- [Akismet]( Configure Akismet to stop spam
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
# Akismet
GitLab leverages [Akismet]( 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:
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)
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