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