diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6
index aeaaffeaf3c..f6adb5b9413 100644
--- a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6
+++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6
@@ -14,6 +14,14 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
actions: {
recordAdded: function(arg) {
this.get("model").unshiftObject(arg);
+ },
+
+ rollUp: function() {
+ var self = this;
+ this.set("loading", true)
+ return Discourse.ScreenedIpAddress.rollUp().then(function() {
+ self.send("show");
+ });
}
}
});
diff --git a/app/assets/javascripts/admin/models/screened_ip_address.js b/app/assets/javascripts/admin/models/screened_ip_address.js
index 985ee81c394..22ff7d91539 100644
--- a/app/assets/javascripts/admin/models/screened_ip_address.js
+++ b/app/assets/javascripts/admin/models/screened_ip_address.js
@@ -51,5 +51,9 @@ Discourse.ScreenedIpAddress.reopenClass({
return Discourse.ScreenedIpAddress.create(b);
});
});
+ },
+
+ rollUp: function() {
+ return Discourse.ajax("/admin/logs/screened_ip_addresses/roll_up", { type: "POST" });
}
});
diff --git a/app/assets/javascripts/admin/templates/logs/screened_ip_addresses.hbs b/app/assets/javascripts/admin/templates/logs/screened_ip_addresses.hbs
index 438e51b3950..4a4350fafaa 100644
--- a/app/assets/javascripts/admin/templates/logs/screened_ip_addresses.hbs
+++ b/app/assets/javascripts/admin/templates/logs/screened_ip_addresses.hbs
@@ -1,5 +1,5 @@
{{i18n admin.logs.screened_ips.description}}
-
+
{{screened-ip-address-form action="recordAdded"}}
diff --git a/app/assets/javascripts/discourse/templates/components/screened-ip-address-form.hbs b/app/assets/javascripts/discourse/templates/components/screened-ip-address-form.hbs
index 6b497d6ab5a..d4a7df9a967 100644
--- a/app/assets/javascripts/discourse/templates/components/screened-ip-address-form.hbs
+++ b/app/assets/javascripts/discourse/templates/components/screened-ip-address-form.hbs
@@ -1,4 +1,4 @@
{{i18n admin.logs.screened_ips.form.label}}
{{text-field value=ip_address disabled=formSubmitted class="ip-address-input" placeholderKey="admin.logs.screened_ips.form.ip_address" autocorrect="off" autocapitalize="off"}}
{{combo-box content=actionNames value=actionName}}
-
+
diff --git a/app/controllers/admin/screened_ip_addresses_controller.rb b/app/controllers/admin/screened_ip_addresses_controller.rb
index efd9e9ce9ab..e9c25aa4025 100644
--- a/app/controllers/admin/screened_ip_addresses_controller.rb
+++ b/app/controllers/admin/screened_ip_addresses_controller.rb
@@ -29,6 +29,58 @@ class Admin::ScreenedIpAddressesController < Admin::AdminController
render json: success_json
end
+ def roll_up
+ # 1 - retrieve all subnets that needs roll up
+ sql = <<-SQL
+ SELECT network(inet(host(ip_address) || './24')) AS ip_range
+ FROM screened_ip_addresses
+ WHERE action_type = :action_type
+ AND family(ip_address) = 4
+ AND masklen(ip_address) = 32
+ GROUP BY ip_range
+ HAVING COUNT(*) >= :min_count
+ SQL
+
+ subnets = ScreenedIpAddress.exec_sql(sql,
+ action_type: ScreenedIpAddress.actions[:block],
+ min_count: SiteSetting.min_ban_entries_for_roll_up).values.flatten
+
+ subnets.each do |subnet|
+ # 2 - create subnet if not already exists
+ ScreenedIpAddress.new(ip_address: subnet).save unless ScreenedIpAddress.where(ip_address: subnet).first
+
+ # 3 - update stats
+ sql = <<-SQL
+ UPDATE screened_ip_addresses
+ SET match_count = sum_match_count,
+ created_at = min_created_at,
+ last_match_at = max_last_match_at
+ FROM (
+ SELECT SUM(match_count) AS sum_match_count,
+ MIN(created_at) AS min_created_at,
+ MAX(last_match_at) AS max_last_match_at
+ FROM screened_ip_addresses
+ WHERE action_type = :action_type
+ AND family(ip_address) = 4
+ AND masklen(ip_address) = 32
+ AND ip_address << :ip_address
+ ) s
+ WHERE ip_address = :ip_address
+ SQL
+
+ ScreenedIpAddress.exec_sql(sql, action_type: ScreenedIpAddress.actions[:block], ip_address: subnet)
+
+ # 4 - remove old matches
+ ScreenedIpAddress.where(action_type: ScreenedIpAddress.actions[:block])
+ .where("family(ip_address) = 4")
+ .where("masklen(ip_address) = 32")
+ .where("ip_address << ?", subnet)
+ .delete_all
+ end
+
+ render json: success_json
+ end
+
private
def allowed_params
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 05b18c4f818..91a1dc961ff 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1870,6 +1870,9 @@ en:
label: "New:"
ip_address: "IP address"
add: "Add"
+ roll_up:
+ text: "Roll up"
+ title: "Creates new subnet ban entries if there are at least 'min_ban_entries_for_roll_up' entries."
logster:
title: "Error Logs"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index e4aceb309dc..1f5b0ef100a 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -942,6 +942,7 @@ en:
levenshtein_distance_spammer_emails: "When matching spammer emails, number of characters difference that will still allow a fuzzy match."
max_new_accounts_per_registration_ip: "If there are already (n) trust level 0 accounts from this IP (and none is a staff member or at TL2 or higher), stop accepting new signups from that IP."
+ min_ban_entries_for_roll_up: "When clicking the Roll up button, will create a new subnet ban entry if there are at least (N) entries."
reply_by_email_enabled: "Enable replying to topics via email."
reply_by_email_address: "Template for reply by email incoming email address, for example: %{reply_key}@reply.example.com or replies+%{reply_key}@example.com"
diff --git a/config/routes.rb b/config/routes.rb
index 854b009101d..78f923d67b4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -101,7 +101,11 @@ Discourse::Application.routes.draw do
scope "/logs" do
resources :staff_action_logs, only: [:index]
resources :screened_emails, only: [:index, :destroy]
- resources :screened_ip_addresses, only: [:index, :create, :update, :destroy]
+ resources :screened_ip_addresses, only: [:index, :create, :update, :destroy] do
+ collection do
+ post "roll_up"
+ end
+ end
resources :screened_urls, only: [:index]
end
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 8ce50b26fd4..6bd32e1d087 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -577,6 +577,7 @@ spam:
min: 0
max: 3
max_new_accounts_per_registration_ip: 3
+ min_ban_entries_for_roll_up: 5
rate_limits:
unique_posts_mins:
diff --git a/spec/controllers/admin/screened_ip_addresses_controller_spec.rb b/spec/controllers/admin/screened_ip_addresses_controller_spec.rb
index edd6af13bb2..ae56646f490 100644
--- a/spec/controllers/admin/screened_ip_addresses_controller_spec.rb
+++ b/spec/controllers/admin/screened_ip_addresses_controller_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe Admin::ScreenedIpAddressesController do
+
it "is a subclass of AdminController" do
(Admin::ScreenedIpAddressesController < Admin::AdminController).should == true
end
@@ -8,15 +9,35 @@ describe Admin::ScreenedIpAddressesController do
let!(:user) { log_in(:admin) }
describe 'index' do
- before do
- xhr :get, :index
- end
-
- subject { response }
- it { should be_success }
it 'returns JSON' do
- ::JSON.parse(subject.body).should be_a(Array)
+ xhr :get, :index
+ response.should be_success
+ JSON.parse(response.body).should be_a(Array)
end
+
end
+
+ describe 'roll_up' do
+
+ it "works" do
+ SiteSetting.expects(:min_ban_entries_for_roll_up).returns(3)
+
+ Fabricate(:screened_ip_address, ip_address: "1.2.3.4", match_count: 1)
+ Fabricate(:screened_ip_address, ip_address: "1.2.3.5", match_count: 1)
+ Fabricate(:screened_ip_address, ip_address: "1.2.3.6", match_count: 1)
+
+ Fabricate(:screened_ip_address, ip_address: "42.42.42.4", match_count: 1)
+ Fabricate(:screened_ip_address, ip_address: "42.42.42.5", match_count: 1)
+
+ xhr :post, :roll_up
+ response.should be_success
+
+ subnet = ScreenedIpAddress.where(ip_address: "1.2.3.0/24").first
+ subnet.should be_present
+ subnet.match_count.should == 3
+ end
+
+ end
+
end