FEATURE: restrict admin access based on IP address

This commit is contained in:
Neil Lalonde 2014-09-04 18:50:27 -04:00
parent 1040a88389
commit ca5f361d0a
12 changed files with 118 additions and 10 deletions

View File

@ -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(),

View File

@ -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')) {

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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 <b>{{sentTo}}</b>. 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 <b>{{currentEmail}}</b>. 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"

View File

@ -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 <b>%{email}</b>. 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}"

View File

@ -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
{

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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