diff --git a/lib/api/v3/custom_options/custom_options_api.rb b/lib/api/v3/custom_options/custom_options_api.rb index c95395f738..43540d0ae5 100644 --- a/lib/api/v3/custom_options/custom_options_api.rb +++ b/lib/api/v3/custom_options/custom_options_api.rb @@ -37,12 +37,26 @@ module API end helpers do - def authorize_view_in_activated_project(custom_option) + def authorize_custom_option_visibility(custom_option) + case custom_option.custom_field + when WorkPackageCustomField + authorized_work_package_option(custom_option) + when ProjectCustomField + authorize_any(%i[view_project], global: true) { raise API::Errors::NotFound } + when TimeEntryCustomField + authorize_any(%i[log_time log_own_time], global: true) { raise API::Errors::NotFound } + when UserCustomField, GroupCustomField + true + else + raise API::Errors::NotFound + end + end + + def authorized_work_package_option(custom_option) allowed = Project - .allowed_to(current_user, :view_work_packages) - .joins(:work_package_custom_fields) - .where(custom_fields: { id: custom_option.custom_field_id }) - .exists? + .allowed_to(current_user, :view_work_packages) + .joins(:work_package_custom_fields) + .exists?(custom_fields: { id: custom_option.custom_field_id }) unless allowed raise API::Errors::NotFound @@ -53,7 +67,7 @@ module API get do co = CustomOption.find(params[:id]) - authorize_view_in_activated_project(co) + authorize_custom_option_visibility(co) CustomOptionRepresenter.new(co, current_user:) end diff --git a/modules/costs/spec/lib/api/v3/time_entries/schemas/time_entry_schema_representer_spec.rb b/modules/costs/spec/lib/api/v3/time_entries/schemas/time_entry_schema_representer_spec.rb index 3b7b5287a3..2a25aa797f 100644 --- a/modules/costs/spec/lib/api/v3/time_entries/schemas/time_entry_schema_representer_spec.rb +++ b/modules/costs/spec/lib/api/v3/time_entries/schemas/time_entry_schema_representer_spec.rb @@ -268,7 +268,7 @@ describe ::API::V3::TimeEntries::Schemas::TimeEntrySchemaRepresenter do end context 'custom value' do - let(:custom_field) { build_stubbed(:time_entry_custom_field) } + let(:custom_field) { build_stubbed(:text_time_entry_custom_field) } let(:path) { "customField#{custom_field.id}" } before do diff --git a/modules/costs/spec/lib/api/v3/time_entries/time_entry_representer_rendering_spec.rb b/modules/costs/spec/lib/api/v3/time_entries/time_entry_representer_rendering_spec.rb index 6fb622f93b..83a1a304be 100644 --- a/modules/costs/spec/lib/api/v3/time_entries/time_entry_representer_rendering_spec.rb +++ b/modules/costs/spec/lib/api/v3/time_entries/time_entry_representer_rendering_spec.rb @@ -255,7 +255,7 @@ describe ::API::V3::TimeEntries::TimeEntryRepresenter, 'rendering' do end context 'custom value' do - let(:custom_field) { build_stubbed(:time_entry_custom_field) } + let(:custom_field) { build_stubbed(:text_time_entry_custom_field) } let(:custom_value) do CustomValue.new(custom_field:, value: '1234', diff --git a/modules/costs/spec/requests/api/time_entries/create_form_resource_spec.rb b/modules/costs/spec/requests/api/time_entries/create_form_resource_spec.rb index f89e383ec9..de73bc8530 100644 --- a/modules/costs/spec/requests/api/time_entries/create_form_resource_spec.rb +++ b/modules/costs/spec/requests/api/time_entries/create_form_resource_spec.rb @@ -39,7 +39,7 @@ describe ::API::V3::TimeEntries::CreateFormAPI, content_type: :json do TimeEntryActivitiesProject.insert(project_id: project.id, activity_id: tea.id, active: false) end end - let(:custom_field) { create(:time_entry_custom_field) } + let(:custom_field) { create(:text_time_entry_custom_field) } let(:user) do create(:user, member_in_project: project, diff --git a/modules/costs/spec/requests/api/time_entries/update_form_resource_spec.rb b/modules/costs/spec/requests/api/time_entries/update_form_resource_spec.rb index 403eccd67a..eaddbe7dba 100644 --- a/modules/costs/spec/requests/api/time_entries/update_form_resource_spec.rb +++ b/modules/costs/spec/requests/api/time_entries/update_form_resource_spec.rb @@ -40,7 +40,7 @@ describe ::API::V3::TimeEntries::UpdateFormAPI, content_type: :json do TimeEntryActivitiesProject.insert(project_id: project.id, activity_id: tea.id, active: false) end end - let(:custom_field) { create(:time_entry_custom_field) } + let(:custom_field) { create(:text_time_entry_custom_field) } let(:user) do create(:user, member_in_project: project, diff --git a/modules/costs/spec/requests/api/time_entry_resource_spec.rb b/modules/costs/spec/requests/api/time_entry_resource_spec.rb index b47a7f2431..f284c79314 100644 --- a/modules/costs/spec/requests/api/time_entry_resource_spec.rb +++ b/modules/costs/spec/requests/api/time_entry_resource_spec.rb @@ -54,7 +54,7 @@ describe 'API v3 time_entry resource', type: :request do let(:other_project) { other_work_package.project } let(:role) { create(:role, permissions:) } let(:permissions) { %i(view_time_entries view_work_packages) } - let(:custom_field) { create(:time_entry_custom_field) } + let(:custom_field) { create(:text_time_entry_custom_field) } let(:custom_value) do CustomValue.create(custom_field:, value: '1234', diff --git a/spec/factories/custom_field_factory.rb b/spec/factories/custom_field_factory.rb index e9c004cac1..366633704d 100644 --- a/spec/factories/custom_field_factory.rb +++ b/spec/factories/custom_field_factory.rb @@ -136,6 +136,47 @@ FactoryBot.define do end end + factory :group_custom_field, class: 'GroupCustomField' do + sequence(:name) { |n| "User Custom Field #{n}" } + type { 'GroupCustomField' } + + factory :boolean_group_custom_field do + name { 'BooleanGroupCustomField' } + field_format { 'bool' } + end + + factory :integer_group_custom_field do + name { 'IntegerGroupCustomField' } + field_format { 'int' } + end + + factory :text_group_custom_field do + name { 'TextGroupCustomField' } + field_format { 'text' } + end + + factory :string_group_custom_field do + name { 'StringGroupCustomField' } + field_format { 'string' } + end + + factory :float_group_custom_field do + name { 'FloatGroupCustomField' } + field_format { 'float' } + end + + factory :list_group_custom_field do + name { 'ListGroupCustomField' } + field_format { 'list' } + possible_values { ['1', '2', '3', '4', '5', '6', '7'] } + end + + factory :date_group_custom_field do + name { 'DateGroupCustomField' } + field_format { 'date' } + end + end + factory :wp_custom_field, class: 'WorkPackageCustomField' do sequence(:name) { |n| "Work package custom field #{n}" } type { 'WorkPackageCustomField' } @@ -208,8 +249,44 @@ FactoryBot.define do end factory :time_entry_custom_field, class: 'TimeEntryCustomField' do - field_format { 'text' } - sequence(:name) { |n| "TimeEntryCustomField #{n}" } + sequence(:name) { |n| "User Custom Field #{n}" } + type { 'TimeEntryCustomField' } + + factory :boolean_time_entry_custom_field do + name { 'BooleanTimeEntryCustomField' } + field_format { 'bool' } + end + + factory :integer_time_entry_custom_field do + name { 'IntegerTimeEntryCustomField' } + field_format { 'int' } + end + + factory :text_time_entry_custom_field do + name { 'TextTimeEntryCustomField' } + field_format { 'text' } + end + + factory :string_time_entry_custom_field do + name { 'StringTimeEntryCustomField' } + field_format { 'string' } + end + + factory :float_time_entry_custom_field do + name { 'FloatTimeEntryCustomField' } + field_format { 'float' } + end + + factory :list_time_entry_custom_field do + name { 'ListTimeEntryCustomField' } + field_format { 'list' } + possible_values { ['A', 'B'] } + end + + factory :date_time_entry_custom_field do + name { 'DateTimeEntryCustomField' } + field_format { 'date' } + end end factory :version_custom_field, class: 'VersionCustomField' do diff --git a/spec/requests/api/v3/custom_options/custom_options_resource_spec.rb b/spec/requests/api/v3/custom_options/custom_options_resource_spec.rb index 84fb8ba18b..501a8e50f5 100644 --- a/spec/requests/api/v3/custom_options/custom_options_resource_spec.rb +++ b/spec/requests/api/v3/custom_options/custom_options_resource_spec.rb @@ -29,29 +29,19 @@ require 'spec_helper' require 'rack/test' -describe 'API v3 Custom Options resource' do +describe 'API v3 Custom Options resource', :aggregate_failures do include Rack::Test::Methods include API::V3::Utilities::PathHelper + shared_let(:project) { create(:project) } let(:user) do create(:user, member_in_project: project, member_through_role: role) end - let(:project) { create(:project) } let(:role) { create(:role, permissions:) } - let(:permissions) { [:view_work_packages] } - let(:custom_field) do - cf = create(:list_wp_custom_field) - project.work_package_custom_fields << cf - - cf - end - let(:custom_option) do - create(:custom_option, - custom_field:) - end + let(:modification) { nil } subject(:response) { last_response } @@ -59,56 +49,178 @@ describe 'API v3 Custom Options resource' do let(:path) { api_v3_paths.custom_option custom_option.id } before do + modification&.call allow(User) .to receive(:current) - .and_return(user) + .and_return(user) get path end - context 'when being allowed' do - it 'is successful' do - expect(subject.status) - .to be(200) + describe 'WorkPackageCustomField' do + shared_let(:custom_field) do + cf = create(:list_wp_custom_field) + + project.work_package_custom_fields << cf + + cf + end + shared_let(:custom_option) do + create(:custom_option, + custom_field:) + end + + context 'when being allowed' do + let(:permissions) { [:view_work_packages] } + + it 'is successful' do + expect(subject.status) + .to be(200) + + expect(response.body) + .to be_json_eql('CustomOption'.to_json) + .at_path('_type') + + expect(response.body) + .to be_json_eql(custom_option.id.to_json) + .at_path('id') + + expect(response.body) + .to be_json_eql(custom_option.value.to_json) + .at_path('value') + end end - it 'returns the custom option' do - expect(response.body) - .to be_json_eql('CustomOption'.to_json) - .at_path('_type') + context 'when lacking permission' do + let(:permissions) { [] } - expect(response.body) - .to be_json_eql(custom_option.id.to_json) - .at_path('id') + it 'is 404' do + expect(subject.status) + .to be(404) + end + end - expect(response.body) - .to be_json_eql(custom_option.value.to_json) - .at_path('value') + context 'when custom option not in project' do + let(:permissions) { [:view_work_packages] } + let(:modification) do + -> do + project.work_package_custom_fields = [] + project.save! + end + end + + it 'is 404' do + expect(subject.status) + .to be(404) + end end end - context 'when lacking permission' do + describe 'ProjectCustomField' do + shared_let(:custom_field) { create(:list_project_custom_field) } + shared_let(:custom_option) { create(:custom_option, custom_field:) } + + context 'when being allowed' do + let(:permissions) { [:view_project] } + + it 'is successful' do + expect(subject.status) + .to be(200) + + expect(response.body) + .to be_json_eql('CustomOption'.to_json) + .at_path('_type') + + expect(response.body) + .to be_json_eql(custom_option.id.to_json) + .at_path('id') + + expect(response.body) + .to be_json_eql(custom_option.value.to_json) + .at_path('value') + end + end + + context 'when lacking permission' do + let(:user) { User.anonymous } + let(:permissions) { [] } + + it 'is 404' do + expect(subject.status) + .to be(404) + end + end + end + + describe 'TimeEntryCustomField' do + shared_let(:custom_field) { create(:list_time_entry_custom_field) } + shared_let(:custom_option) { create(:custom_option, custom_field:) } + + context 'when being allowed with log_time' do + let(:permissions) { [:log_time] } + + it 'is successful' do + expect(subject.status) + .to be(200) + + expect(response.body) + .to be_json_eql('CustomOption'.to_json) + .at_path('_type') + + expect(response.body) + .to be_json_eql(custom_option.id.to_json) + .at_path('id') + + expect(response.body) + .to be_json_eql(custom_option.value.to_json) + .at_path('value') + end + end + + context 'when being allowed with log_own_time' do + let(:permissions) { [:log_own_time] } + + it 'is successful' do + expect(subject.status) + .to be(200) + end + end + + context 'when lacking permission' do + let(:user) { User.anonymous } + let(:permissions) { [] } + + it 'is 404' do + expect(subject.status) + .to be(404) + end + end + end + + describe 'UserCustomField' do + shared_let(:custom_field) { create(:list_user_custom_field) } + shared_let(:custom_option) { create(:custom_option, custom_field:) } let(:permissions) { [] } - it 'is 404' do + it 'is successful' do expect(subject.status) - .to be(404) + .to be(200) end end - context 'when custom option not in project' do - let(:custom_field) do - # not added to project - create(:list_wp_custom_field) - end + describe 'GroupCustomField' do + shared_let(:custom_field) { create(:list_group_custom_field) } + shared_let(:custom_option) { create(:custom_option, custom_field:) } + let(:permissions) { [] } - it 'is 404' do + it 'is successful' do expect(subject.status) - .to be(404) + .to be(200) end end context 'when not existing' do let(:path) { api_v3_paths.custom_option 0 } + let(:permissions) { [:view_work_packages] } it 'is 404' do expect(subject.status)