Fixed issue #1563 - prevent admin from locking out.

This commit is contained in:
Martin Edenhofer 2017-10-20 13:32:52 +02:00
parent 93345553ad
commit 2c39eb47de
6 changed files with 263 additions and 87 deletions

View file

@ -10,12 +10,12 @@ class Role < ApplicationModel
include Role::Assets include Role::Assets
has_and_belongs_to_many :users, after_add: :cache_update, after_remove: :cache_update has_and_belongs_to_many :users, after_add: :cache_update, after_remove: :cache_update
has_and_belongs_to_many :permissions, after_add: :cache_update, after_remove: :cache_update, before_add: :validate_agent_limit has_and_belongs_to_many :permissions, after_add: :cache_update, after_remove: :cache_update, before_add: :validate_agent_limit_by_permission, before_remove: :last_admin_check_by_permission
validates :name, presence: true validates :name, presence: true
store :preferences store :preferences
before_create :validate_permissions before_create :validate_permissions
before_update :validate_permissions before_update :validate_permissions, :last_admin_check_by_attribute, :validate_agent_limit_by_attributes
association_attributes_ignored :users association_attributes_ignored :users
@ -102,24 +102,52 @@ returns
=end =end
def self.with_permissions(keys) def self.with_permissions(keys)
permission_ids = Role.permission_ids_by_name(keys)
Role.joins(:roles_permissions).joins(:permissions).where(
'permissions_roles.permission_id IN (?) AND roles.active = ? AND permissions.active = ?', permission_ids, true, true
).distinct()
end
=begin
check if roles is with permission
role = Role.find(123)
role.with_permission?('admin.session')
get if role has permission of "admin.session" or "ticket.agent"
role.with_permission?(['admin.session', 'ticket.agent'])
returns
true | false
=end
def with_permission?(keys)
permission_ids = Role.permission_ids_by_name(keys)
return true if Role.joins(:roles_permissions).joins(:permissions).where(
'roles.id = ? AND permissions_roles.permission_id IN (?) AND permissions.active = ?', id, permission_ids, true
).distinct().count.nonzero?
false
end
private_class_method
def self.permission_ids_by_name(keys)
if keys.class != Array if keys.class != Array
keys = [keys] keys = [keys]
end end
roles = []
permission_ids = [] permission_ids = []
keys.each do |key| keys.each do |key|
Object.const_get('Permission').with_parents(key).each do |local_key| ::Permission.with_parents(key).each do |local_key|
permission = Object.const_get('Permission').lookup(name: local_key) permission = ::Permission.lookup(name: local_key)
next if !permission next if !permission
permission_ids.push permission.id permission_ids.push permission.id
end end
next if permission_ids.empty?
Role.joins(:roles_permissions).joins(:permissions).where('permissions_roles.permission_id IN (?) AND roles.active = ? AND permissions.active = ?', permission_ids, true, true).distinct().each do |role|
roles.push role
end
end end
return [] if roles.empty? permission_ids
roles
end end
private private
@ -140,14 +168,47 @@ returns
true true
end end
def validate_agent_limit(permission) def last_admin_check_by_attribute
return true if !Setting.get('system_agent_limit') return true if !will_save_change_to_attribute?('active')
return true if permission.name != 'ticket.agent' return true if active != false
return true if !with_permission?(['admin', 'admin.user'])
raise Exceptions::UnprocessableEntity, 'Minimum one user needs to have admin permissions.' if last_admin_check_admin_count < 1
true
end
ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent' }).pluck(:id) def last_admin_check_by_permission(permission)
return true if Setting.get('import_mode')
return true if permission.name != 'admin' && permission.name != 'admin.user'
raise Exceptions::UnprocessableEntity, 'Minimum one user needs to have admin permissions.' if last_admin_check_admin_count < 1
true
end
def last_admin_check_admin_count
admin_role_ids = Role.joins(:permissions).where(permissions: { name: ['admin', 'admin.user'], active: true }, roles: { active: true }).where.not(id: id).pluck(:id)
User.joins(:roles).where(roles: { id: admin_role_ids }, users: { active: true }).count
end
def validate_agent_limit_by_attributes
return true if !Setting.get('system_agent_limit')
return true if !will_save_change_to_attribute?('active')
return true if active != true
return true if !with_permission?('ticket.agent')
ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent', active: true }, roles: { active: true }).pluck(:id)
currents = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).pluck(:id)
news = User.joins(:roles).where(roles: { id: id }, users: { active: true }).pluck(:id)
count = currents.concat(news).uniq.count
raise Exceptions::UnprocessableEntity, 'Agent limit exceeded, please check your account settings.' if count > Setting.get('system_agent_limit')
true
end
def validate_agent_limit_by_permission(permission)
return true if !Setting.get('system_agent_limit')
return true if active != true
return true if permission.active != true
return true if permission.name != 'ticket.agent'
ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent' }, roles: { active: true }).pluck(:id)
ticket_agent_role_ids.push(id) ticket_agent_role_ids.push(id)
count = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).count count = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).count
raise Exceptions::UnprocessableEntity, 'Agent limit exceeded, please check your account settings.' if count > Setting.get('system_agent_limit') raise Exceptions::UnprocessableEntity, 'Agent limit exceeded, please check your account settings.' if count > Setting.get('system_agent_limit')
true true
end end

View file

@ -40,12 +40,12 @@ class User < ApplicationModel
before_validation :check_name, :check_email, :check_login, :ensure_uniq_email, :ensure_password, :ensure_roles, :ensure_identifier before_validation :check_name, :check_email, :check_login, :ensure_uniq_email, :ensure_password, :ensure_roles, :ensure_identifier
before_create :check_preferences_default, :validate_roles, :validate_ooo, :domain_based_assignment, :set_locale before_create :check_preferences_default, :validate_roles, :validate_ooo, :domain_based_assignment, :set_locale
before_update :check_preferences_default, :validate_roles, :validate_ooo, :reset_login_failed before_update :check_preferences_default, :validate_roles, :validate_ooo, :reset_login_failed, :validate_agent_limit_by_attributes, :last_admin_check_by_attribute
after_create :avatar_for_email_check after_create :avatar_for_email_check
after_update :avatar_for_email_check after_update :avatar_for_email_check
after_destroy :avatar_destroy, :user_device_destroy after_destroy :avatar_destroy, :user_device_destroy
has_and_belongs_to_many :roles, after_add: [:cache_update, :check_notifications], after_remove: :cache_update, before_add: :validate_agent_limit, before_remove: :last_admin_check, class_name: 'Role' has_and_belongs_to_many :roles, after_add: [:cache_update, :check_notifications], after_remove: :cache_update, before_add: :validate_agent_limit_by_role, before_remove: :last_admin_check_by_role, class_name: 'Role'
has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update, class_name: 'Organization' has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update, class_name: 'Organization'
#has_many :permissions, class_name: 'Permission', through: :roles, class_name: 'Role' #has_many :permissions, class_name: 'Permission', through: :roles, class_name: 'Role'
has_many :tokens, after_add: :cache_update, after_remove: :cache_update has_many :tokens, after_add: :cache_update, after_remove: :cache_update
@ -283,7 +283,7 @@ returns
sleep 1 sleep 1
user.login_failed += 1 user.login_failed += 1
user.save user.save!
nil nil
end end
@ -977,10 +977,10 @@ returns
raise Exceptions::UnprocessableEntity, 'out of office no such replacement user' if !User.find_by(id: out_of_office_replacement_id) raise Exceptions::UnprocessableEntity, 'out of office no such replacement user' if !User.find_by(id: out_of_office_replacement_id)
true true
end end
=begin =begin
checks if the current user is the last one checks if the current user is the last one with admin permissions.
with admin permissions.
Raises Raises
@ -988,28 +988,47 @@ raise 'Minimum one user need to have admin permissions'
=end =end
def last_admin_check(role) def last_admin_check_by_attribute
return true if Setting.get('import_mode') return true if !will_save_change_to_attribute?('active')
return true if active != false
ticket_admin_role_ids = Role.joins(:permissions).where(permissions: { name: ['admin', 'admin.user'] }).pluck(:id) return true if !permissions?(['admin', 'admin.user'])
count = User.joins(:roles).where(roles: { id: ticket_admin_role_ids }, users: { active: true }).count raise Exceptions::UnprocessableEntity, 'Minimum one user needs to have admin permissions.' if last_admin_check_admin_count < 1
if ticket_admin_role_ids.include?(role.id)
count -= 1
end
raise Exceptions::UnprocessableEntity, 'Minimum one user needs to have admin permissions.' if count < 1
true true
end end
def validate_agent_limit(role) def last_admin_check_by_role(role)
return true if !Setting.get('system_agent_limit') return true if Setting.get('import_mode')
return true if !role.with_permission?(['admin', 'admin.user'])
raise Exceptions::UnprocessableEntity, 'Minimum one user needs to have admin permissions.' if last_admin_check_admin_count < 1
true
end
ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent' }).pluck(:id) def last_admin_check_admin_count
admin_role_ids = Role.joins(:permissions).where(permissions: { name: ['admin', 'admin.user'], active: true }, roles: { active: true }).pluck(:id)
User.joins(:roles).where(roles: { id: admin_role_ids }, users: { active: true }).count - 1
end
def validate_agent_limit_by_attributes
return true if !Setting.get('system_agent_limit')
return true if !will_save_change_to_attribute?('active')
return true if active != true
return true if !permissions?('ticket.agent')
ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent', active: true }, roles: { active: true }).pluck(:id)
count = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).count + 1
raise Exceptions::UnprocessableEntity, 'Agent limit exceeded, please check your account settings.' if count > Setting.get('system_agent_limit')
true
end
def validate_agent_limit_by_role(role)
return true if !Setting.get('system_agent_limit')
return true if active != true
return true if role.active != true
return true if !role.with_permission?('ticket.agent')
ticket_agent_role_ids = Role.joins(:permissions).where(permissions: { name: 'ticket.agent', active: true }, roles: { active: true }).pluck(:id)
count = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).count count = User.joins(:roles).where(roles: { id: ticket_agent_role_ids }, users: { active: true }).count
if ticket_agent_role_ids.include?(role.id) if ticket_agent_role_ids.include?(role.id)
count += 1 count += 1
end end
raise Exceptions::UnprocessableEntity, 'Agent limit exceeded, please check your account settings.' if count > Setting.get('system_agent_limit') raise Exceptions::UnprocessableEntity, 'Agent limit exceeded, please check your account settings.' if count > Setting.get('system_agent_limit')
true true
end end

View file

@ -91,7 +91,7 @@ class RoleTest < ActiveSupport::TestCase
test 'permission default' do test 'permission default' do
roles = Role.with_permissions('not_existing') roles = Role.with_permissions('not_existing')
assert(roles.empty?) assert(roles.blank?)
roles = Role.with_permissions('admin') roles = Role.with_permissions('admin')
assert_equal('Admin', roles.first.name) assert_equal('Admin', roles.first.name)
@ -116,4 +116,26 @@ class RoleTest < ActiveSupport::TestCase
end end
test 'with permission' do
permission_test1 = Permission.create_or_update(
name: 'test-with-permission1',
note: 'parent test permission 1',
)
permission_test2 = Permission.create_or_update(
name: 'test-with-permission2',
note: 'parent test permission 2',
)
name = rand(999_999_999)
role = Role.create(
name: "Test with Permission? #{name}",
note: "Test with Permission? #{name} Role.",
permissions: [permission_test2],
updated_by_id: 1,
created_by_id: 1
)
assert_not(role.with_permission?('test-with-permission1'))
assert(role.with_permission?('test-with-permission2'))
assert(role.with_permission?(['test-with-permission2', 'some_other_permission']))
end
end end

View file

@ -4,47 +4,48 @@ require 'test_helper'
class RoleValidateAgentLimit < ActiveSupport::TestCase class RoleValidateAgentLimit < ActiveSupport::TestCase
test 'role_validate_agent_limit' do test 'role_validate_agent_limit' do
users = User.of_role('Agent') agent_max = User.with_permissions('ticket.agent').count
UserInfo.current_user_id = 1 UserInfo.current_user_id = 1
Setting.set('system_agent_limit', users.count + 2) Setting.set('system_agent_limit', agent_max + 2)
permission_ticket_agent = Permission.where(name: 'ticket.agent') permission_ticket_agent = Permission.where(name: 'ticket.agent')
role_agent_limit_success = Role.create( role_agent_limit_success = Role.create!(
name: 'agent-limit-test-success', name: 'agent-limit-test-success',
note: 'agent-limit-test-success Role.', note: 'agent-limit-test-success Role.',
permissions: [], permissions: [],
updated_by_id: 1, active: true,
created_by_id: 1
) )
role_agent_limit_fail = Role.create( role_agent_limit_fail = Role.create!(
name: 'agent-limit-test-fail', name: 'agent-limit-test-fail',
note: 'agent-limit-test-fail Role.', note: 'agent-limit-test-fail Role.',
permissions: [], permissions: [],
updated_by_id: 1, active: true,
created_by_id: 1
) )
user1 = User.create( user1 = User.create!(
firstname: 'Firstname', firstname: 'Firstname',
lastname: 'Lastname', lastname: 'Lastname',
email: 'some@example.com', email: 'some-agentlimit-role@example.com',
login: 'some-agentlimit@example.com', login: 'some-agentlimit-role@example.com',
roles: [role_agent_limit_success], roles: [role_agent_limit_success],
active: true,
) )
user2 = User.create( user2 = User.create!(
firstname: 'Firstname1', firstname: 'Firstname1',
lastname: 'Lastname1', lastname: 'Lastname1',
email: 'some-agentlimit-1@example.com', email: 'some-agentlimit-role-1@example.com',
login: 'some-agentlimit-1@example.com', login: 'some-agentlimit-role-1@example.com',
roles: [role_agent_limit_success], roles: [role_agent_limit_success],
active: true,
) )
user3 = User.create( user3 = User.create!(
firstname: 'Firstname2', firstname: 'Firstname2',
lastname: 'Lastname2', lastname: 'Lastname2',
email: 'some-agentlimit-2@example.com', email: 'some-agentlimit-role-2@example.com',
login: 'some-agentlimit-2@example.com', login: 'some-agentlimit-role-2@example.com',
roles: [role_agent_limit_fail], roles: [role_agent_limit_fail],
active: true,
) )
role_agent_limit_success.permissions = permission_ticket_agent role_agent_limit_success.permissions = permission_ticket_agent
@ -52,11 +53,21 @@ class RoleValidateAgentLimit < ActiveSupport::TestCase
role_agent_limit_fail.permissions = permission_ticket_agent role_agent_limit_fail.permissions = permission_ticket_agent
end end
user1.destroy role_agent_limit_fail.active = false
user2.destroy role_agent_limit_fail.save!
user3.destroy
role_agent_limit_success.destroy role_agent_limit_fail.permissions = permission_ticket_agent
role_agent_limit_fail.destroy
assert_raises(Exceptions::UnprocessableEntity) do
role_agent_limit_fail.active = true
role_agent_limit_fail.save!
end
user1.destroy!
user2.destroy!
user3.destroy!
role_agent_limit_success.destroy!
role_agent_limit_fail.destroy!
Setting.set('system_agent_limit', nil) Setting.set('system_agent_limit', nil)
end end
end end

View file

@ -864,6 +864,25 @@ class UserTest < ActiveSupport::TestCase
admin_count_inital = User.with_permissions('admin').count admin_count_inital = User.with_permissions('admin').count
assert_equal(1, admin_count_inital) assert_equal(1, admin_count_inital)
assert_raises(Exceptions::UnprocessableEntity) do
admin3.active = false
admin3.save!
end
assert_equal(1, User.with_permissions('admin').count)
admin_role = Role.find_by(name: 'Admin')
assert_raises(Exceptions::UnprocessableEntity) do
admin_role.active = false
admin_role.save!
end
assert_raises(Exceptions::UnprocessableEntity) do
admin_role.permission_revoke('admin')
end
assert_equal(1, User.with_permissions('admin').count)
end end
test 'only valid agent in group permission check' do test 'only valid agent in group permission check' do

View file

@ -4,52 +4,96 @@ require 'test_helper'
class UserValidateAgentLimit < ActiveSupport::TestCase class UserValidateAgentLimit < ActiveSupport::TestCase
test 'user_validate_agent_limit' do test 'user_validate_agent_limit' do
users = User.of_role('Agent')
UserInfo.current_user_id = 1 UserInfo.current_user_id = 1
Setting.set('system_agent_limit', users.count + 2) agent_max = User.with_permissions('ticket.agent').count + 2
Setting.set('system_agent_limit', agent_max)
role_agent = Role.lookup(name: 'Agent') role_agent = Role.lookup(name: 'Agent')
role_customer = Role.lookup(name: 'Customer') role_customer = Role.lookup(name: 'Customer')
user1 = User.create( user1 = User.create!(
firstname: 'Firstname', firstname: 'Firstname',
lastname: 'Lastname', lastname: 'Lastname',
email: 'some@example.com', email: 'some-agentlimit-user@example.com',
login: 'some-agentlimit@example.com', login: 'some-agentlimit-user@example.com',
roles: [role_agent], roles: [role_agent],
active: true,
) )
user2 = User.create( user2 = User.create!(
firstname: 'Firstname1', firstname: 'Firstname1',
lastname: 'Lastname1', lastname: 'Lastname1',
email: 'some-agentlimit-1@example.com', email: 'some-agentlimit-user-1@example.com',
login: 'some-agentlimit-1@example.com', login: 'some-agentlimit-user-1@example.com',
roles: [role_agent], roles: [role_agent],
active: true,
) )
assert_raises(Exceptions::UnprocessableEntity) do assert_raises(Exceptions::UnprocessableEntity) do
user3 = User.create( user3 = User.create!(
firstname: 'Firstname2', firstname: 'Firstname2',
lastname: 'Lastname2', lastname: 'Lastname2',
email: 'some-agentlimit-2@example.com', email: 'some-agentlimit-user-2@example.com',
login: 'some-agentlimit-2@example.com', login: 'some-agentlimit-user-2@example.com',
roles: [role_agent], roles: [role_agent],
active: true,
) )
end end
user3 = User.create( user3 = User.create!(
firstname: 'Firstname2', firstname: 'Firstname2',
lastname: 'Lastname2', lastname: 'Lastname2',
email: 'some-agentlimit-2@example.com', email: 'some-agentlimit-user-2@example.com',
login: 'some-agentlimit-2@example.com', login: 'some-agentlimit-user-2@example.com',
roles: [role_customer], roles: [role_customer],
active: true,
) )
assert_raises(Exceptions::UnprocessableEntity) do assert_raises(Exceptions::UnprocessableEntity) do
user3.roles = [role_agent] user3.roles = [role_agent]
end end
user1.destroy assert_equal(User.with_permissions('ticket.agent').count, agent_max)
user2.destroy
user3.destroy Setting.set('system_agent_limit', agent_max + 1)
user3.reload
user3.roles = [role_agent]
user3.save!
user3.active = false
user3.save!
Setting.set('system_agent_limit', agent_max)
# try to activate inactive agent again
assert_raises(Exceptions::UnprocessableEntity) do
user3 = User.find(user3.id)
user3.active = true
user3.save!
end
assert_equal(User.with_permissions('ticket.agent').count, agent_max)
# try to activate inactive role again
role_agent_limit = Role.create!(
name: 'agent-limit-test-invalid-role',
note: 'agent-limit-test-invalid-role Role.',
permissions: Permission.where(name: 'ticket.agent'),
active: false,
)
user3.roles = [role_agent_limit]
user3.active = true
user3.save!
assert_raises(Exceptions::UnprocessableEntity) do
role_agent_limit.active = true
role_agent_limit.save!
end
assert_equal(User.with_permissions('ticket.agent').count, agent_max)
user1.destroy!
user2.destroy!
user3.destroy!
role_agent_limit.destroy!
Setting.set('system_agent_limit', nil) Setting.set('system_agent_limit', nil)
end end
end end