diff --git a/app/assets/javascripts/admin/components/screened_ip_address_form_component.js b/app/assets/javascripts/admin/components/screened_ip_address_form_component.js index f752031ff40..69c517f00bd 100644 --- a/app/assets/javascripts/admin/components/screened_ip_address_form_component.js +++ b/app/assets/javascripts/admin/components/screened_ip_address_form_component.js @@ -21,7 +21,8 @@ Discourse.ScreenedIpAddressFormComponent = Ember.Component.extend({ actionNames: function() { return [ {id: 'block', name: I18n.t('admin.logs.screened_ips.actions.block')}, - {id: 'do_nothing', name: I18n.t('admin.logs.screened_ips.actions.do_nothing')} + {id: 'do_nothing', name: I18n.t('admin.logs.screened_ips.actions.do_nothing')}, + {id: 'allow_admin', name: I18n.t('admin.logs.screened_ips.actions.allow_admin')} ]; }.property(), diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index 38d9ff794d4..2381a97c4b9 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -147,6 +147,12 @@ export default DiscourseController.extend(ModalFunctionality, { this.set('authenticate', null); return; } + if (options.not_allowed_from_ip_address) { + this.send('showLogin'); + this.flash(I18n.t('login.not_allowed_from_ip_address'), 'success'); + this.set('authenticate', null); + return; + } // Reload the page if we're authenticated if (options.authenticated) { if (window.location.pathname === Discourse.getURL('/login')) { diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index 6e437101c1b..b63d5f8c6f5 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -84,6 +84,11 @@ class SessionController < ApplicationController return end + if ScreenedIpAddress.block_login?(user, request.remote_ip) + not_allowed_from_ip_address(user) + return + end + (user.active && user.email_confirmed?) ? login(user) : not_activated(user) end @@ -151,6 +156,10 @@ class SessionController < ApplicationController } end + def not_allowed_from_ip_address(user) + render json: {error: I18n.t("login.not_allowed_from_ip_address", username: user.username)} + end + def failed_to_login(user) message = user.suspend_reason ? "login.suspended_with_reason" : "login.suspended" diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index e59a96279d0..d77c84843c9 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -81,8 +81,9 @@ class Users::OmniauthCallbacksController < ApplicationController user.toggle(:active).save end - # log on any account that is active with forum access - if Guardian.new(user).can_access_forum? && user.active + if ScreenedIpAddress.block_login?(user, request.remote_ip) + @data.not_allowed_from_ip_address = true + elsif Guardian.new(user).can_access_forum? && user.active # log on any account that is active with forum access log_on_user(user) Invite.invalidate_for_email(user.email) # invite link can't be used to log in anymore session[:authentication] = nil # don't carry around old auth info, perhaps move elsewhere diff --git a/app/models/screened_ip_address.rb b/app/models/screened_ip_address.rb index b168c0a46fc..fdca1ab5560 100644 --- a/app/models/screened_ip_address.rb +++ b/app/models/screened_ip_address.rb @@ -76,10 +76,19 @@ class ScreenedIpAddress < ActiveRecord::Base exists_for_ip_address_and_action?(ip_address, actions[:do_nothing]) end - def self.exists_for_ip_address_and_action?(ip_address, action_type) + def self.exists_for_ip_address_and_action?(ip_address, action_type, opts={}) b = match_for_ip_address(ip_address) - b.record_match! if b - !!b and b.action_type == action_type + found = (!!b and b.action_type == action_type) + b.record_match! if found and opts[:record_match] != false + found + end + + def self.block_login?(user, ip_address) + return false if user.nil? + return false if !user.admin? + return false if ScreenedIpAddress.where(action_type: actions[:allow_admin]).count == 0 + return true if ip_address.nil? + !exists_for_ip_address_and_action?(ip_address, actions[:allow_admin], record_match: false) end end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 9a08f957328..7b9bf51f7e6 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -601,6 +601,7 @@ en: awaiting_approval: "Your account has not been approved by a staff member yet. You will be sent an email when it is approved." requires_invite: "Sorry, access to this forum is by invite only." not_activated: "You can't log in yet. We previously sent an activation email to you at {{sentTo}}. Please follow the instructions in that email to activate your account." + not_allowed_from_ip_address: "You can't login from that IP address." resend_activation_email: "Click here to send the activation email again." sent_activation_email_again: "We sent another activation email to you at {{currentEmail}}. It might take a few minutes for it to arrive; be sure to check your spam folder." google: @@ -1799,6 +1800,7 @@ en: actions: block: "Block" do_nothing: "Allow" + allow_admin: "Allow Admin" form: label: "New:" ip_address: "IP address" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 248484e2b2f..53670b1555b 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1082,6 +1082,7 @@ en: active: "Your account is activated and ready to use." activate_email: "You're almost done! We sent an activation email to %{email}. Please follow the instructions in the email to activate your account." not_activated: "You can't log in yet. We sent an activation email to you. Please follow the instructions in the email to activate your account." + not_allowed_from_ip_address: "You can't login as %{username} from that IP address." suspended: "You can't log in until %{date}." suspended_with_reason: "You can't log in until %{date}. The reason you were suspended: %{reason}" errors: "%{errors}" diff --git a/lib/auth/result.rb b/lib/auth/result.rb index 26e067dcecc..3b0a36b5de5 100644 --- a/lib/auth/result.rb +++ b/lib/auth/result.rb @@ -2,7 +2,7 @@ class Auth::Result attr_accessor :user, :name, :username, :email, :user, :email_valid, :extra_data, :awaiting_activation, :awaiting_approval, :authenticated, :authenticator_name, - :requires_invite + :requires_invite, :not_allowed_from_ip_address def session_data { @@ -22,7 +22,8 @@ class Auth::Result { authenticated: !!authenticated, awaiting_activation: !!awaiting_activation, - awaiting_approval: !!awaiting_approval + awaiting_approval: !!awaiting_approval, + not_allowed_from_ip_address: !!not_allowed_from_ip_address } else { diff --git a/lib/current_user.rb b/lib/current_user.rb index dc81ca43ca0..f2a07813e1b 100644 --- a/lib/current_user.rb +++ b/lib/current_user.rb @@ -27,7 +27,8 @@ module CurrentUser end def current_user - current_user_provider.current_user + c = current_user_provider.current_user + ScreenedIpAddress.block_login?(c, request.remote_ip) ? nil : c end private diff --git a/lib/screening_model.rb b/lib/screening_model.rb index 9fb02f6a1f3..26ed789c6c4 100644 --- a/lib/screening_model.rb +++ b/lib/screening_model.rb @@ -3,7 +3,7 @@ module ScreeningModel module ClassMethods def actions - @actions ||= Enum.new(:block, :do_nothing) + @actions ||= Enum.new(:block, :do_nothing, :allow_admin) end def default_action(action_key) diff --git a/spec/controllers/session_controller_spec.rb b/spec/controllers/session_controller_spec.rb index e40ebef55d5..2f2134df5b3 100644 --- a/spec/controllers/session_controller_spec.rb +++ b/spec/controllers/session_controller_spec.rb @@ -291,6 +291,36 @@ describe SessionController do end end end + + context 'when admins are restricted by ip address' do + let(:permitted_ip_address) { '111.234.23.11' } + + before do + Fabricate(:screened_ip_address, ip_address: permitted_ip_address, action_type: ScreenedIpAddress.actions[:allow_admin]) + end + + it 'is successful for admin at the ip address' do + User.any_instance.stubs(:admin?).returns(true) + ActionDispatch::Request.any_instance.stubs(:remote_ip).returns(permitted_ip_address) + xhr :post, :create, login: user.username, password: 'myawesomepassword' + session[:current_user_id].should == user.id + end + + it 'returns an error for admin not at the ip address' do + User.any_instance.stubs(:admin?).returns(true) + ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("111.234.23.12") + xhr :post, :create, login: user.username, password: 'myawesomepassword' + JSON.parse(response.body)['error'].should be_present + session[:current_user_id].should_not == user.id + end + + it 'is successful for non-admin not at the ip address' do + User.any_instance.stubs(:admin?).returns(false) + ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("111.234.23.12") + xhr :post, :create, login: user.username, password: 'myawesomepassword' + session[:current_user_id].should == user.id + end + end end context 'when email has not been confirmed' do diff --git a/spec/models/screened_ip_address_spec.rb b/spec/models/screened_ip_address_spec.rb index 222ade19c10..b860419b618 100644 --- a/spec/models/screened_ip_address_spec.rb +++ b/spec/models/screened_ip_address_spec.rb @@ -235,4 +235,51 @@ describe ScreenedIpAddress do end end end + + describe '#block_login?' do + context 'no allow_admin records exist' do + it "returns false when user is nil" do + described_class.block_login?(nil, '123.12.12.12').should == false + end + + it "returns false for non-admin user" do + described_class.block_login?(Fabricate.build(:user), '123.12.12.12').should == false + end + + it "returns false for admin user" do + described_class.block_login?(Fabricate.build(:admin), '123.12.12.12').should == false + end + + it "returns false for admin user and ip_address arg is nil" do + described_class.block_login?(Fabricate.build(:admin), nil).should == false + end + end + + context 'allow_admin record exists' do + before do + @permitted_ip_address = '111.234.23.11' + Fabricate(:screened_ip_address, ip_address: @permitted_ip_address, action_type: described_class.actions[:allow_admin]) + end + + it "returns false when user is nil" do + described_class.block_login?(nil, @permitted_ip_address).should == false + end + + it "returns false for an admin user at the allowed ip address" do + described_class.block_login?(Fabricate.build(:admin), @permitted_ip_address).should == false + end + + it "returns true for an admin user at another ip address" do + described_class.block_login?(Fabricate.build(:admin), '123.12.12.12').should == true + end + + it "returns false for regular user at allowed ip address" do + described_class.block_login?(Fabricate.build(:user), @permitted_ip_address).should == false + end + + it "returns false for regular user at another ip address" do + described_class.block_login?(Fabricate.build(:user), '123.12.12.12').should == false + end + end + end end