diff --git a/ee/app/graphql/resolvers/ai/catalog/item_resolver.rb b/ee/app/graphql/resolvers/ai/catalog/item_resolver.rb index 31c2b1824fc348eeac74a5c6fc52d87db7db98bc..0f8c93463f1465d5a5a1c5cdb661fa28b7a3a0e5 100644 --- a/ee/app/graphql/resolvers/ai/catalog/item_resolver.rb +++ b/ee/app/graphql/resolvers/ai/catalog/item_resolver.rb @@ -15,7 +15,7 @@ class ItemResolver < BaseResolver def resolve(id:) Gitlab::Graphql::Lazy.with_value(find_object(id: id)) do |item| - next if item&.deleted? + next if item&.deleted? && !user_has_item_configured?(item) item end @@ -26,6 +26,24 @@ def resolve(id:) def find_object(id:) GitlabSchema.find_by_gid(id) end + + def user_has_item_configured?(item) + return false unless current_user + + # Allow viewing deleted items only if the user has access to at least one + # project/group where the item is configured + item.consumers.any? do |consumer| + if consumer.project + Ability.allowed?(current_user, :read_ai_catalog_item, item) && + Ability.allowed?(current_user, :read_project, consumer.project) + elsif consumer.group + Ability.allowed?(current_user, :read_ai_catalog_item, item) && + Ability.allowed?(current_user, :read_group, consumer.group) + else + false + end + end + end end end end diff --git a/ee/spec/requests/api/graphql/ai/catalog/item_spec.rb b/ee/spec/requests/api/graphql/ai/catalog/item_spec.rb index 95f0ecf0bad36e2a3e349312e09cd06bae69410f..e82bc64c2902e5410a77050df49b58e486d330c4 100644 --- a/ee/spec/requests/api/graphql/ai/catalog/item_spec.rb +++ b/ee/spec/requests/api/graphql/ai/catalog/item_spec.rb @@ -111,14 +111,109 @@ end context 'with a deleted catalog item' do - let_it_be(:catalog_item) { create(:ai_catalog_item, project: project, deleted_at: 1.day.ago) } + let_it_be(:catalog_item) { create(:ai_catalog_item, project: project, public: true, deleted_at: 1.day.ago) } - context 'when owner' do - let(:current_user) do - create(:user).tap { |user| project.add_owner(user) } + context 'when the item has no consumers' do + context 'when owner' do + let(:current_user) do + create(:user).tap { |user| project.add_owner(user) } + end + + it_behaves_like 'an unsuccessful query' + end + + context 'when developer' do + let(:current_user) do + create(:user).tap { |user| project.add_developer(user) } + end + + it_behaves_like 'an unsuccessful query' + end + + context 'when reporter' do + let(:current_user) do + create(:user).tap { |user| project.add_reporter(user) } + end + + it_behaves_like 'an unsuccessful query' + end + + context 'when anonymous user' do + let(:current_user) { nil } + + it_behaves_like 'an unsuccessful query' + end + end + + context 'when the item has consumers (configured in a project)' do + let_it_be(:consumer_project) { create(:project) } + let_it_be(:item_consumer) { create(:ai_catalog_item_consumer, item: catalog_item, project: consumer_project) } + + context 'when developer with access to consumer project' do + let(:current_user) do + create(:user).tap { |user| consumer_project.add_developer(user) } + end + + it 'returns the deleted item' do + post_graphql(query, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(data).to match( + hash_including( + 'id' => catalog_item.to_global_id.to_s, + 'name' => catalog_item.name + ) + ) + end end - it_behaves_like 'an unsuccessful query' + context 'when maintainer with access to consumer project' do + let(:current_user) do + create(:user).tap { |user| consumer_project.add_maintainer(user) } + end + + it 'returns the deleted item' do + post_graphql(query, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(data).to match( + hash_including( + 'id' => catalog_item.to_global_id.to_s, + 'name' => catalog_item.name + ) + ) + end + end + + context 'when reporter with access to consumer project' do + let(:current_user) do + create(:user).tap { |user| consumer_project.add_reporter(user) } + end + + it 'returns the deleted item' do + post_graphql(query, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(data).to match( + hash_including( + 'id' => catalog_item.to_global_id.to_s, + 'name' => catalog_item.name + ) + ) + end + end + + context 'when user without access to consumer project' do + let(:current_user) { create(:user) } + + it_behaves_like 'an unsuccessful query' + end + + context 'when anonymous user' do + let(:current_user) { nil } + + it_behaves_like 'an unsuccessful query' + end end end end