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