diff --git a/app/models/concerns/authz/has_roles.rb b/app/models/concerns/authz/has_roles.rb new file mode 100644 index 0000000000000000000000000000000000000000..9f2e36c319a36f2b12b3eb6dd44e865613451afb --- /dev/null +++ b/app/models/concerns/authz/has_roles.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Authz + module HasRoles + extend ActiveSupport::Concern + + def can_assign_role?(current_user, access_level) + return true unless access_level + + max_access_level = max_member_access_for_user(current_user) + + Gitlab::Access.level_encompasses?(current_access_level: max_access_level, level_to_assign: access_level) + end + end +end + +Authz::HasRoles.prepend_mod diff --git a/app/models/group.rb b/app/models/group.rb index bb4fc39eaa1b139cb0f078c2161c959625fd2948..64266618f962a0e26b97ad604c458bb54cba6c31 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -6,6 +6,7 @@ class Group < Namespace include Gitlab::ConfigHelper include AfterCommitQueue include AccessRequestable + include Authz::HasRoles include Avatarable include SelectForProjectAuthorization include LoadedInGroupList @@ -1292,15 +1293,6 @@ def pending_delete? deletion_schedule.marked_for_deletion_on.future? end - def assigning_role_too_high?(current_user, access_level) - return false if current_user.can_admin_all_resources? - return false unless access_level - - max_access_level = max_member_access(current_user) - - !Authz::Role.access_level_encompasses?(current_access_level: max_access_level, level_to_assign: access_level) - end - private def feature_flag_enabled_for_self_or_ancestor?(feature_flag, type: :development) diff --git a/app/models/member.rb b/app/models/member.rb index cd8b822ce5c99809a7c353e3d4f501a234e279fc..6c6beaafa082e3bd7c45b8eb8df791d43f607057 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -666,13 +666,12 @@ def prevent_role_assignement?(current_user, params) current_access_level = params[:current_access_level] # check if it's a valid downgrade, if the member's current access level encompasses the target level - return false if Authz::Role.access_level_encompasses?( + return false if Gitlab::Access.level_encompasses?( current_access_level: current_access_level, level_to_assign: assigning_access_level ) - # prevent assignement in case the role access level is higher than current user's role - source.assigning_role_too_high?(current_user, assigning_access_level) + !source.can_assign_role?(current_user, assigning_access_level) end private diff --git a/app/models/project.rb b/app/models/project.rb index a5a5867adf2cdf5426e2ff4dca2edcf24703c6ad..fe183ac66a3411f2e3c95e7eb11406a11dbe98d4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -6,6 +6,7 @@ class Project < ApplicationRecord include Gitlab::ConfigHelper include Gitlab::VisibilityLevel include AccessRequestable + include Authz::HasRoles include Avatarable include CacheMarkdownField include Sortable @@ -3698,15 +3699,6 @@ def ensure_pool_repository pool_repository || create_new_pool_repository end - def assigning_role_too_high?(current_user, access_level) - return false unless access_level - return false if current_user.can_admin_all_resources? - - max_access_level = max_member_access_for_user(current_user) - - !Authz::Role.access_level_encompasses?(current_access_level: max_access_level, level_to_assign: access_level) - end - private def check_duplicate_export!(current_user) diff --git a/app/services/group_access_tokens/rotate_service.rb b/app/services/group_access_tokens/rotate_service.rb index ed1ae40993ff05c6de0d8e612303c0bcc71eb056..35609d2d378d27faa67ddb12a83bb2f51d01e979 100644 --- a/app/services/group_access_tokens/rotate_service.rb +++ b/app/services/group_access_tokens/rotate_service.rb @@ -8,16 +8,10 @@ class RotateService < ::PersonalAccessTokens::RotateService override :valid_access_level? def valid_access_level? - return true if current_user.can_admin_all_resources? return false unless current_user.can?(:manage_resource_access_tokens, group) token_access_level = group.max_member_access_for_user(token.user).to_i - current_user_access_level = group.max_member_access_for_user(current_user).to_i - - Authz::Role.access_level_encompasses?( - current_access_level: current_user_access_level, - level_to_assign: token_access_level - ) + group.can_assign_role?(current_user, token_access_level) end private diff --git a/app/services/project_access_tokens/rotate_service.rb b/app/services/project_access_tokens/rotate_service.rb index 938a21f9e45e232df84760301169a184a592bca1..01e10859930332631f94cb18d5eb65fb70b56093 100644 --- a/app/services/project_access_tokens/rotate_service.rb +++ b/app/services/project_access_tokens/rotate_service.rb @@ -8,16 +8,10 @@ class RotateService < ::PersonalAccessTokens::RotateService override :valid_access_level? def valid_access_level? - return true if current_user.can_admin_all_resources? return false unless current_user.can?(:manage_resource_access_tokens, project) - token_access_level = project.team.max_member_access(token.user.id).to_i - current_user_access_level = project.team.max_member_access(current_user.id).to_i - - Authz::Role.access_level_encompasses?( - current_access_level: current_user_access_level, - level_to_assign: token_access_level - ) + token_access_level = project.max_member_access_for_user(token.user).to_i + project.can_assign_role?(current_user, token_access_level) end private diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb index 9ab0635ce4e11d8ec163b819d020a633a78eb772..2414eb64c39486e62dcb33f500e76a7d40089b8a 100644 --- a/app/services/resource_access_tokens/create_service.rb +++ b/app/services/resource_access_tokens/create_service.rb @@ -147,12 +147,7 @@ def success(access_token) def validate_access_level(access_level) return true if current_user.bot? - user_access_level = resource.max_member_access_for_user(current_user) - - Authz::Role.access_level_encompasses?( - current_access_level: user_access_level, - level_to_assign: access_level.to_i - ) + resource.can_assign_role?(current_user, access_level) end end end diff --git a/ee/app/models/concerns/ee/authz/has_roles.rb b/ee/app/models/concerns/ee/authz/has_roles.rb new file mode 100644 index 0000000000000000000000000000000000000000..ece17641a1e4558500d8bce03ffc358b55074ecd --- /dev/null +++ b/ee/app/models/concerns/ee/authz/has_roles.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module EE + module Authz + module HasRoles + def custom_role_abilities_too_high?(current_user:, target_member_role_id:) + return false if current_user.can_admin_all_resources? + return false unless target_member_role_id + + current_user_access_level = max_member_access_for_user(current_user) + + return false if ::Gitlab::Access::OWNER == current_user_access_level + + current_user_member_role = ::Member.highest_role(current_user, self)&.member_role + target_member_role = MemberRole.find_by_id(target_member_role_id) + + current_user_role_abilities = member_role_abilities(current_user_member_role) + + custom_abilities_included_with_base_access_level(current_user_access_level) + + target_member_role_abilities = member_role_abilities(target_member_role) + + (target_member_role_abilities - current_user_role_abilities).present? + end + + private + + def custom_abilities_included_with_base_access_level(access_level) + abilities = [] + customizable_permissions = MemberRole.all_customizable_permissions + enabled_for_key = :"enabled_for_#{self.class.name.demodulize.downcase}_access_levels" + + customizable_permissions.each do |name, definition| + next unless definition.fetch(enabled_for_key, []).include?(access_level) + + abilities << name + end + + abilities + end + + def member_role_abilities(member_role) + return [] unless member_role + + member_role.enabled_permissions.keys + end + end + end +end diff --git a/ee/app/models/ee/member.rb b/ee/app/models/ee/member.rb index 7eb2a67953c32e60253ba3a17dca28e8ea75c562..fa46af1cd5f1bf72dfde29bd593df1e021855106 100644 --- a/ee/app/models/ee/member.rb +++ b/ee/app/models/ee/member.rb @@ -242,56 +242,18 @@ def update_user_group_member_roles(old_values_map: nil) override :prevent_role_assignement? def prevent_role_assignement?(current_user, params) - return false if current_user.can_admin_all_resources? + target_member_role_id = params[:member_role_id] || member_role_id - member_role_id = params[:member_role_id] || member_role_id - - # first we need to check if there are possibly more custom abilities than current user has - return true if custom_role_abilities_too_high?(current_user, member_role_id) + return true if source.custom_role_abilities_too_high?( + current_user: current_user, + target_member_role_id: target_member_role_id + ) super end private - def custom_role_abilities_too_high?(current_user, member_role_id) - return false unless member_role_id - - current_user_access_level = source.max_member_access_for_user(current_user) - return false if ::Gitlab::Access::OWNER == current_user_access_level - - current_member_role = ::Member.highest_role(current_user, source)&.member_role - current_member_role_abilities = member_role_abilities( - current_member_role) + custom_abilities_included_with_base_access_level(current_user_access_level) - - new_member_role = MemberRole.find_by_id(member_role_id) - new_member_role_abilities = member_role_abilities(new_member_role) - - (new_member_role_abilities - current_member_role_abilities).present? - end - - def custom_abilities_included_with_base_access_level(current_access_level) - abilities = [] - - customizable_permissions = MemberRole.all_customizable_permissions - - enabled_for_key = :"enabled_for_#{source.class.name.demodulize.downcase}_access_levels" - - customizable_permissions.each do |name, definition| - next unless definition.fetch(enabled_for_key, []).include?(current_access_level) - - abilities << name - end - - abilities - end - - def member_role_abilities(member_role) - return [] unless member_role - - member_role.enabled_permissions.keys - end - def group_allowed_email_domains return [] unless group diff --git a/ee/app/models/ee/user.rb b/ee/app/models/ee/user.rb index 9658f2869b0e063d8ac2443d16beeac0ee79fbce..ae924aad25dd0d046ba13e93770afee67bf07eb3 100644 --- a/ee/app/models/ee/user.rb +++ b/ee/app/models/ee/user.rb @@ -960,7 +960,7 @@ def audit_unlock_access(author: self) end def has_admin_custom_permissions? - Authz::Admin.new(self).available_permissions_for_user.present? + ::Authz::Admin.new(self).available_permissions_for_user.present? end end end diff --git a/ee/app/services/ee/members/destroy_service.rb b/ee/app/services/ee/members/destroy_service.rb index 81eabfe6ac1e4d5552318e98762b0de940d15570..32de1c9c16caff4cd8378d31edebc75dbf854e65 100644 --- a/ee/app/services/ee/members/destroy_service.rb +++ b/ee/app/services/ee/members/destroy_service.rb @@ -156,8 +156,8 @@ def destroy_user_group_member_roles(member) return unless group.is_a?(Group) return unless member.member_role_id || - Authz::UserGroupMemberRole.for_user_in_group_and_shared_groups(user, group).exists? || - Authz::UserProjectMemberRole.for_user_shared_with_group(user, group).exists? + ::Authz::UserGroupMemberRole.for_user_in_group_and_shared_groups(user, group).exists? || + ::Authz::UserProjectMemberRole.for_user_shared_with_group(user, group).exists? ::Authz::UserGroupMemberRoles::DestroyForGroupWorker.perform_async(user.id, group.id) end diff --git a/lib/authz/role.rb b/lib/authz/role.rb index 65f8a86a02d5033795da7967ac4b3be3d2c8bae0..64f98d97127b964e29ce3ddba296b9d0d436fee3 100644 --- a/lib/authz/role.rb +++ b/lib/authz/role.rb @@ -2,23 +2,11 @@ module Authz class Role - def self.access_level_encompasses?(current_access_level:, level_to_assign:) - # Roles that don't follow the inherited hierarchy can only be assigned by owners - # or by users with the same role - non_hierarchy_roles = [Gitlab::Access::PLANNER] - - if non_hierarchy_roles.include?(level_to_assign) - return current_access_level.in?([Gitlab::Access::OWNER, level_to_assign]) - end - - level_to_assign.to_i <= current_access_level.to_i - end - def self.roles_user_can_assign(current_access_level, roles = nil) available_roles = roles || Gitlab::Access.options_with_owner available_roles.select do |_role_name, access_level| - access_level_encompasses?(current_access_level: current_access_level, level_to_assign: access_level) + Gitlab::Access.level_encompasses?(current_access_level: current_access_level, level_to_assign: access_level) end end end diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index dc5fa5bebef3697199d8f25ee836d5dac7ab2f7c..a74b12701b75c1c647567d3bb3957f7a012153ce 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -40,6 +40,18 @@ module Access class << self delegate :values, to: :options + def level_encompasses?(current_access_level:, level_to_assign:) + # Roles that don't follow the inherited hierarchy can only be assigned by owners + # or by users with the same role + non_hierarchy_roles = [Gitlab::Access::PLANNER] + + if non_hierarchy_roles.include?(level_to_assign) + return current_access_level.in?([Gitlab::Access::OWNER, level_to_assign]) + end + + level_to_assign.to_i <= current_access_level.to_i + end + def all_values options_with_owner.values end diff --git a/spec/lib/authz/role_spec.rb b/spec/lib/authz/role_spec.rb index 3f49eecaff1f4010ccb20c6d7c207338c21fac47..f8a86296ba378c979a268b9bafa6a089e66f69ff 100644 --- a/spec/lib/authz/role_spec.rb +++ b/spec/lib/authz/role_spec.rb @@ -3,52 +3,10 @@ require 'spec_helper' RSpec.describe Authz::Role, feature_category: :system_access do - assignable_roles = { - owner: [:owner, :maintainer, :planner, :developer, :reporter, :guest], - maintainer: [:maintainer, :developer, :reporter, :guest], - developer: [:developer, :reporter, :guest], - reporter: [:reporter, :guest], - planner: [:planner, :guest], - guest: [:guest] - } - - describe '.access_level_encompasses?' do - def access_level_encompasses?(current_level, level_to_assign) - described_class.access_level_encompasses?( - current_access_level: access_level_value(current_level), - level_to_assign: access_level_value(level_to_assign) - ) - end - - assignable_roles.each do |current_level, expected| - context "with #{current_level}" do - not_expected = Gitlab::Access.sym_options_with_owner.keys - expected - - expected.each do |level_to_assign| - it "encompasses #{level_to_assign}" do - expect(access_level_encompasses?(current_level, level_to_assign)).to be(true) - end - end - - not_expected.each do |level_to_assign| - it "does not encompass #{level_to_assign}" do - expect(access_level_encompasses?(current_level, level_to_assign)).to be(false) - end - end - end - end - - it 'returns false when current_access_level is nil' do - result = described_class.access_level_encompasses?( - current_access_level: nil, - level_to_assign: Gitlab::Access::MAINTAINER - ) - expect(result).to be(false) - end - end + include RolesHelpers describe '.roles_user_can_assign' do - assignable_roles.each do |current_level, expected| + RolesHelpers.assignable_roles.each do |current_level, expected| context "with #{current_level}" do it 'returns correct assignable roles' do # Use the actual module's constants directly diff --git a/spec/lib/gitlab/access_spec.rb b/spec/lib/gitlab/access_spec.rb index aee39192e62b83658f4cc1ae0cceff409a03145d..37fa9e8dcd6b4876d297db5fef1475d56afa2d9c 100644 --- a/spec/lib/gitlab/access_spec.rb +++ b/spec/lib/gitlab/access_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::Access, feature_category: :permissions do + include RolesHelpers + let_it_be(:member) { create(:group_member, :developer) } describe '#role_description' do @@ -12,4 +14,41 @@ expect(member.role_description).to eq(role) end end + + describe '.level_encompasses?' do + def level_encompasses?(current_level, level_to_assign) + described_class.level_encompasses?( + current_access_level: access_level_value(current_level), + level_to_assign: access_level_value(level_to_assign) + ) + end + + RolesHelpers.assignable_roles.each do |current_level, expected| + context "with #{current_level}" do + not_expected = Gitlab::Access.sym_options_with_owner.keys - expected + + expected.each do |level_to_assign| + it "encompasses #{level_to_assign}" do + result = level_encompasses?(current_level, level_to_assign) + expect(result).to be(true) + end + end + + not_expected.each do |level_to_assign| + it "does not encompass #{level_to_assign}" do + result = level_encompasses?(current_level, level_to_assign) + expect(result).to be(false) + end + end + end + end + + it 'returns false when current_access_level is nil' do + result = described_class.level_encompasses?( + current_access_level: nil, + level_to_assign: Gitlab::Access::MAINTAINER + ) + expect(result).to be(false) + end + end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 4e00c592301becd61b4927caddacb45645e5b599..19bf68cad1fa4915c002f36c6fefee258d487c7c 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -2302,50 +2302,15 @@ end end - describe '#assigning_role_too_high?' do - let_it_be(:user) { create(:user) } - let_it_be(:group) { create(:group) } - let_it_be(:member, reload: true) { create(:group_member, :reporter, group: group, user: user) } - - subject(:assigning_role_too_high) { group.assigning_role_too_high?(user, access_level) } - - context 'when the access_level is nil' do - let(:access_level) { nil } - - it 'returns false' do - expect(assigning_role_too_high).to be_falsey - end - end - - context 'when the role being assigned is lower then the role of currect user' do - let(:access_level) { Gitlab::Access::GUEST } - - it { is_expected.to be(false) } - end - - context 'when the role being assigned is equal to the role of currect user' do - let(:access_level) { Gitlab::Access::REPORTER } - - it { is_expected.to be(false) } - end - - context 'when the role being assigned is higher than the role of currect user' do - let(:access_level) { Gitlab::Access::MAINTAINER } - - it 'returns true' do - expect(assigning_role_too_high).to be_truthy - end - - context 'when the current user is admin', :enable_admin_mode do - before do - user.update!(admin: true) - end + it_behaves_like '#can_assign_role' do + let(:user) { create(:user) } + let(:resource) { create(:group) } + let!(:membership) { create(:group_member, :reporter, group: resource, user: user) } + end - it 'returns false' do - expect(assigning_role_too_high).to be_falsey - end - end - end + it_behaves_like '#custom_role_abilities_too_high' do + let(:current_user) { create(:user) } + let(:resource) { create(:group) } end def setup_group_members(group) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 142f893571962d1ac65c2d69b79f5bb0b1a9a6c1..d52dd2d99752892340866c62396e35ed74bfeeea 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -10414,50 +10414,15 @@ def create_build(new_pipeline = pipeline, name = 'test') end end - describe '#assigning_role_too_high?' do - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } - let_it_be(:member, reload: true) { create(:project_member, :reporter, project: project, user: user) } - - subject(:assigning_role_too_high) { project.assigning_role_too_high?(user, access_level) } - - context 'when the access_level is nil' do - let(:access_level) { nil } - - it 'returns false' do - expect(assigning_role_too_high).to be_falsey - end - end - - context 'when the role being assigned is lower than the role of current user' do - let(:access_level) { Gitlab::Access::GUEST } - - it { is_expected.to be(false) } - end - - context 'when the role being assigned is equal to the role of current user' do - let(:access_level) { Gitlab::Access::REPORTER } - - it { is_expected.to be(false) } - end - - context 'when the role being assigned is higher than the role of current user' do - let(:access_level) { Gitlab::Access::MAINTAINER } - - it 'returns true' do - expect(assigning_role_too_high).to be_truthy - end - - context 'when the current user is admin', :enable_admin_mode do - before do - user.update!(admin: true) - end + it_behaves_like '#can_assign_role' do + let(:user) { create(:user) } + let(:resource) { create(:project) } + let!(:membership) { create(:project_member, :reporter, project: resource, user: user) } + end - it 'returns false' do - expect(assigning_role_too_high).to be_falsey - end - end - end + it_behaves_like '#custom_role_abilities_too_high' do + let(:current_user) { create(:user) } + let(:resource) { create(:project) } end describe '#owner_entity' do diff --git a/spec/support/helpers/roles_helpers.rb b/spec/support/helpers/roles_helpers.rb new file mode 100644 index 0000000000000000000000000000000000000000..f0eddc74eea5936206b0b16d83c0bd75aad76c1d --- /dev/null +++ b/spec/support/helpers/roles_helpers.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module RolesHelpers + module_function + + def assignable_roles + { + owner: [:owner, :maintainer, :planner, :developer, :reporter, :guest], + maintainer: [:maintainer, :developer, :reporter, :guest], + developer: [:developer, :reporter, :guest], + reporter: [:reporter, :guest], + planner: [:planner, :guest], + guest: [:guest] + } + end + + def access_level_value(name) + Gitlab::Access.sym_options_with_owner[name] + end +end diff --git a/spec/support/shared_examples/authz/has_roles_shared_examples.rb b/spec/support/shared_examples/authz/has_roles_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..cd78cbd4adad6dbe92b5ab10b24130ea03b58847 --- /dev/null +++ b/spec/support/shared_examples/authz/has_roles_shared_examples.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +RSpec.shared_examples '#can_assign_role' do + describe '#can_assign_role?' do + subject(:can_assign_role) { resource.can_assign_role?(user, access_level) } + + context 'when the access_level is nil' do + let(:access_level) { nil } + + it 'returns true' do + expect(can_assign_role).to be(true) + end + end + + context 'when the role being assigned is lower than the role of current user' do + let(:access_level) { Gitlab::Access::GUEST } + + it { is_expected.to be(true) } + end + + context 'when the role being assigned is equal to the role of current user' do + let(:access_level) { Gitlab::Access::REPORTER } + + it { is_expected.to be(true) } + end + + context 'when the role being assigned is higher than the role of current user' do + let(:access_level) { Gitlab::Access::MAINTAINER } + + it 'returns false' do + expect(can_assign_role).to be(false) + end + + context 'when the current user is admin', :enable_admin_mode do + before do + user.update!(admin: true) + end + + it 'returns true' do + expect(can_assign_role).to be(true) + end + end + end + end +end + +RSpec.shared_examples '#custom_role_abilities_too_high' do + describe '#custom_role_abilities_too_high?' do + let(:member_role_id) { nil } + let(:access_level) { Gitlab::Access::GUEST } + + subject(:custom_role_abilities_too_high?) do + resource.custom_role_abilities_too_high?(current_user: current_user, target_member_role_id: member_role_id) + end + + context 'for custom roles assignement' do + let(:member_role_current_user) do + create(:member_role, :maintainer, admin_merge_request: true, namespace: resource.root_ancestor) + end + + let(:member_role_less_abilities) do + create(:member_role, :guest, admin_merge_request: true, namespace: resource.root_ancestor) + end + + let(:member_role_more_abilities) do + create(:member_role, :guest, admin_merge_request: true, admin_push_rules: true, remove_project: true, + namespace: resource.root_ancestor) + end + + before do + current_member = resource.add_maintainer(current_user) + current_member.update!(member_role: member_role_current_user) + stub_licensed_features(custom_roles: true) + end + + context 'with the same custom role as current user has' do + let(:member_role_id) { member_role_current_user.id } + + it 'allows role assignement' do + expect(custom_role_abilities_too_high?).to be(false) + end + end + + context "with custom role abilities included in the current user's base access" do + let(:member_role_included_abilities) do + create(:member_role, :guest, admin_merge_request: true, admin_push_rules: true, + namespace: resource.root_ancestor) + end + + let(:member_role_id) { member_role_included_abilities.id } + + it 'allows role assignement' do + expect(custom_role_abilities_too_high?).to be(false) + end + end + + context 'with the custom role having less abilities than current user has' do + let(:member_role_id) { member_role_less_abilities.id } + + it 'returns false' do + expect(custom_role_abilities_too_high?).to be(false) + end + end + + context 'with the custom role having more abilities than current user has' do + let(:member_role_id) { member_role_more_abilities.id } + + it 'returns true' do + expect(custom_role_abilities_too_high?).to be(true) + end + + context 'when current user is an admin', :enable_admin_mode do + before do + current_user.members.delete_all + + current_user.update!(admin: true) + end + + it 'returns false' do + expect(custom_role_abilities_too_high?).to be(false) + end + end + end + end + end +end