Commit 0755284f authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '340716-confidential-issue-2' into 'master'

Validate anonymous search access in issuable API endpoints

See merge request gitlab-org/gitlab!70290
parents 98b90de6 5d131e75
...@@ -4,6 +4,7 @@ module IssueResolverArguments ...@@ -4,6 +4,7 @@ module IssueResolverArguments
extend ActiveSupport::Concern extend ActiveSupport::Concern
prepended do prepended do
include SearchArguments
include LooksAhead include LooksAhead
argument :iid, GraphQL::Types::String, argument :iid, GraphQL::Types::String,
...@@ -49,9 +50,6 @@ module IssueResolverArguments ...@@ -49,9 +50,6 @@ module IssueResolverArguments
argument :closed_after, Types::TimeType, argument :closed_after, Types::TimeType,
required: false, required: false,
description: 'Issues closed after this date.' description: 'Issues closed after this date.'
argument :search, GraphQL::Types::String,
required: false,
description: 'Search query for issue title or description.'
argument :types, [Types::IssueTypeEnum], argument :types, [Types::IssueTypeEnum],
as: :issue_types, as: :issue_types,
description: 'Filter issues by the given issue types.', description: 'Filter issues by the given issue types.',
...@@ -91,6 +89,7 @@ module IssueResolverArguments ...@@ -91,6 +89,7 @@ module IssueResolverArguments
params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args) params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args)
params_not_mutually_exclusive(args, mutually_exclusive_milestone_args) params_not_mutually_exclusive(args, mutually_exclusive_milestone_args)
params_not_mutually_exclusive(args.fetch(:not, {}), mutually_exclusive_milestone_args) params_not_mutually_exclusive(args.fetch(:not, {}), mutually_exclusive_milestone_args)
validate_anonymous_search_access! if args[:search].present?
super super
end end
......
# frozen_string_literal: true
module SearchArguments
extend ActiveSupport::Concern
included do
argument :search, GraphQL::Types::String,
required: false,
description: 'Search query for title or description.'
end
def validate_anonymous_search_access!
return if current_user.present? || Feature.disabled?(:disable_anonymous_search, type: :ops)
raise ::Gitlab::Graphql::Errors::ArgumentError,
"User must be authenticated to include the `search` argument."
end
end
...@@ -8072,7 +8072,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -8072,7 +8072,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="boardepicancestorsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="boardepicancestorsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="boardepicancestorsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="boardepicancestorsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="boardepicancestorsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. | | <a id="boardepicancestorsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="boardepicancestorssearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="boardepicancestorssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="boardepicancestorssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="boardepicancestorssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="boardepicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="boardepicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
| <a id="boardepicancestorsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. | | <a id="boardepicancestorsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
...@@ -8105,7 +8105,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -8105,7 +8105,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="boardepicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="boardepicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="boardepicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="boardepicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="boardepicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. | | <a id="boardepicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="boardepicchildrensearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="boardepicchildrensearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="boardepicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="boardepicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="boardepicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="boardepicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
| <a id="boardepicchildrenstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. | | <a id="boardepicchildrenstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
...@@ -9477,7 +9477,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -9477,7 +9477,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="epicancestorsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="epicancestorsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="epicancestorsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="epicancestorsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="epicancestorsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. | | <a id="epicancestorsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="epicancestorssearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="epicancestorssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="epicancestorssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="epicancestorssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="epicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="epicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
| <a id="epicancestorsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. | | <a id="epicancestorsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
...@@ -9510,7 +9510,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -9510,7 +9510,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="epicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="epicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="epicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="epicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="epicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. | | <a id="epicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="epicchildrensearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="epicchildrensearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="epicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="epicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="epicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="epicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
| <a id="epicchildrenstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. | | <a id="epicchildrenstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
...@@ -10181,7 +10181,7 @@ Returns [`Epic`](#epic). ...@@ -10181,7 +10181,7 @@ Returns [`Epic`](#epic).
| <a id="groupepicmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="groupepicmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="groupepicmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="groupepicmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="groupepicnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. | | <a id="groupepicnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="groupepicsearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="groupepicsearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="groupepicsort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="groupepicsort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="groupepicstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="groupepicstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
| <a id="groupepicstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. | | <a id="groupepicstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
...@@ -10226,7 +10226,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -10226,7 +10226,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupepicsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="groupepicsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="groupepicsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="groupepicsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="groupepicsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. | | <a id="groupepicsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="groupepicssearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="groupepicssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="groupepicssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="groupepicssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="groupepicsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="groupepicsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
| <a id="groupepicsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. | | <a id="groupepicsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
...@@ -10282,7 +10282,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -10282,7 +10282,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="groupissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="groupissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="groupissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="groupissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="groupissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="groupissuessearch"></a>`search` | [`String`](#string) | Search query for issue title or description. | | <a id="groupissuessearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="groupissuessort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. | | <a id="groupissuessort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. |
| <a id="groupissuesstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. | | <a id="groupissuesstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. |
| <a id="groupissuestypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. | | <a id="groupissuestypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. |
...@@ -12714,7 +12714,7 @@ Returns [`Issue`](#issue). ...@@ -12714,7 +12714,7 @@ Returns [`Issue`](#issue).
| <a id="projectissuemilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="projectissuemilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="projectissuemyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="projectissuemyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="projectissuenot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="projectissuenot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="projectissuesearch"></a>`search` | [`String`](#string) | Search query for issue title or description. | | <a id="projectissuesearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="projectissuesort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. | | <a id="projectissuesort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. |
| <a id="projectissuestate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. | | <a id="projectissuestate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. |
| <a id="projectissuetypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. | | <a id="projectissuetypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. |
...@@ -12747,7 +12747,7 @@ Returns [`IssueStatusCountsType`](#issuestatuscountstype). ...@@ -12747,7 +12747,7 @@ Returns [`IssueStatusCountsType`](#issuestatuscountstype).
| <a id="projectissuestatuscountsmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="projectissuestatuscountsmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="projectissuestatuscountsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="projectissuestatuscountsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="projectissuestatuscountsnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="projectissuestatuscountsnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="projectissuestatuscountssearch"></a>`search` | [`String`](#string) | Search query for issue title or description. | | <a id="projectissuestatuscountssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="projectissuestatuscountstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. | | <a id="projectissuestatuscountstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. |
| <a id="projectissuestatuscountsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Issues updated after this date. | | <a id="projectissuestatuscountsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Issues updated after this date. |
| <a id="projectissuestatuscountsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Issues updated before this date. | | <a id="projectissuestatuscountsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Issues updated before this date. |
...@@ -12784,7 +12784,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -12784,7 +12784,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="projectissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="projectissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="projectissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="projectissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="projectissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="projectissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="projectissuessearch"></a>`search` | [`String`](#string) | Search query for issue title or description. | | <a id="projectissuessearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="projectissuessort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. | | <a id="projectissuessort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. |
| <a id="projectissuesstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. | | <a id="projectissuesstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. |
| <a id="projectissuestypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. | | <a id="projectissuestypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. |
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
module Resolvers module Resolvers
class EpicsResolver < BaseResolver class EpicsResolver < BaseResolver
include TimeFrameArguments include TimeFrameArguments
include SearchArguments
include LooksAhead include LooksAhead
include SetsMaxPageSize include SetsMaxPageSize
...@@ -18,10 +19,6 @@ module Resolvers ...@@ -18,10 +19,6 @@ module Resolvers
required: false, required: false,
description: 'Filter epics by state.' description: 'Filter epics by state.'
argument :search, GraphQL::Types::String,
required: false,
description: 'Search query for epic title or description.'
argument :in, [Types::IssuableSearchableFieldEnum], argument :in, [Types::IssuableSearchableFieldEnum],
required: false, required: false,
description: 'Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.' description: 'Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'
...@@ -74,6 +71,7 @@ module Resolvers ...@@ -74,6 +71,7 @@ module Resolvers
validate_timeframe_params!(args) validate_timeframe_params!(args)
validate_starts_with_iid!(args) validate_starts_with_iid!(args)
validate_search_in_params!(args) validate_search_in_params!(args)
validate_anonymous_search_access! if args[:search].present?
super(**args) super(**args)
end end
......
...@@ -44,6 +44,7 @@ module API ...@@ -44,6 +44,7 @@ module API
end end
[':id/epics', ':id/-/epics'].each do |path| [':id/epics', ':id/-/epics'].each do |path|
get path do get path do
validate_anonymous_search_access! if declared_params[:search].present?
epics = paginate(find_epics(finder_params: { group_id: user_group.id })).with_api_entity_associations epics = paginate(find_epics(finder_params: { group_id: user_group.id })).with_api_entity_associations
# issuable_metadata has to be set because `Entities::Epic` doesn't inherit from `Entities::IssuableEntity` # issuable_metadata has to be set because `Entities::Epic` doesn't inherit from `Entities::IssuableEntity`
......
...@@ -144,6 +144,35 @@ RSpec.describe Resolvers::EpicsResolver do ...@@ -144,6 +144,35 @@ RSpec.describe Resolvers::EpicsResolver do
expect(epics).to match_array([epic2, epic3, epic4]) expect(epics).to match_array([epic2, epic3, epic4])
end end
end end
context 'with anonymous user' do
let_it_be(:current_user) { nil }
context 'with disable_anonymous_search enabled' do
before do
stub_feature_flags(disable_anonymous_search: true)
end
it 'returns an error' do
error_message = "User must be authenticated to include the `search` argument."
expect { resolve_epics(search: 'created') }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, error_message)
end
end
context 'with disable_anonymous_search disabled' do
before do
stub_feature_flags(disable_anonymous_search: false)
end
it 'filters epics by search term' do
epics = resolve_epics(search: 'created')
expect(epics).to match_array([epic1, epic2])
end
end
end
end end
context 'with author_username' do context 'with author_username' do
......
...@@ -199,18 +199,6 @@ RSpec.describe API::Epics do ...@@ -199,18 +199,6 @@ RSpec.describe API::Epics do
expect_paginated_array_response([epic.id]) expect_paginated_array_response([epic.id])
end end
it 'returns epics matching given search string for title' do
get api(url), params: { search: epic2.title }
expect_paginated_array_response([epic2.id])
end
it 'returns epics matching given search string for description' do
get api(url), params: { search: epic2.description }
expect_paginated_array_response([epic2.id])
end
it 'returns epics matching given status' do it 'returns epics matching given status' do
get api(url, user), params: { state: :opened } get api(url, user), params: { state: :opened }
...@@ -379,6 +367,25 @@ RSpec.describe API::Epics do ...@@ -379,6 +367,25 @@ RSpec.describe API::Epics do
expect_paginated_array_response(epic.id) expect_paginated_array_response(epic.id)
end end
context 'with search param' do
it 'returns issues matching given search string for title' do
get api(url, user), params: { search: epic2.title }
expect_paginated_array_response(epic2.id)
end
it 'returns issues matching given search string for description' do
get api(url, user), params: { search: epic2.description }
expect_paginated_array_response(epic2.id)
end
it_behaves_like 'issuable anonymous search' do
let(:issuable) { epic2 }
let(:result) { issuable.id }
end
end
describe "#to_reference" do describe "#to_reference" do
it 'exposes reference path' do it 'exposes reference path' do
get api(url) get api(url)
......
...@@ -624,6 +624,12 @@ module API ...@@ -624,6 +624,12 @@ module API
{} {}
end end
def validate_anonymous_search_access!
return if current_user.present? || Feature.disabled?(:disable_anonymous_search, type: :ops)
unprocessable_entity!('User must be authenticated to use search')
end
private private
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
......
...@@ -114,6 +114,7 @@ module API ...@@ -114,6 +114,7 @@ module API
end end
get '/issues_statistics' do get '/issues_statistics' do
authenticate! unless params[:scope] == 'all' authenticate! unless params[:scope] == 'all'
validate_anonymous_search_access! if params[:search].present?
present issues_statistics, with: Grape::Presenters::Presenter present issues_statistics, with: Grape::Presenters::Presenter
end end
...@@ -131,6 +132,7 @@ module API ...@@ -131,6 +132,7 @@ module API
end end
get do get do
authenticate! unless params[:scope] == 'all' authenticate! unless params[:scope] == 'all'
validate_anonymous_search_access! if params[:search].present?
issues = paginate(find_issues) issues = paginate(find_issues)
options = { options = {
...@@ -169,6 +171,7 @@ module API ...@@ -169,6 +171,7 @@ module API
optional :non_archived, type: Boolean, desc: 'Return issues from non archived projects', default: true optional :non_archived, type: Boolean, desc: 'Return issues from non archived projects', default: true
end end
get ":id/issues" do get ":id/issues" do
validate_anonymous_search_access! if declared_params[:search].present?
issues = paginate(find_issues(group_id: user_group.id, include_subgroups: true)) issues = paginate(find_issues(group_id: user_group.id, include_subgroups: true))
options = { options = {
...@@ -187,6 +190,8 @@ module API ...@@ -187,6 +190,8 @@ module API
use :issues_stats_params use :issues_stats_params
end end
get ":id/issues_statistics" do get ":id/issues_statistics" do
validate_anonymous_search_access! if declared_params[:search].present?
present issues_statistics(group_id: user_group.id, include_subgroups: true), with: Grape::Presenters::Presenter present issues_statistics(group_id: user_group.id, include_subgroups: true), with: Grape::Presenters::Presenter
end end
end end
...@@ -204,6 +209,7 @@ module API ...@@ -204,6 +209,7 @@ module API
use :issues_params use :issues_params
end end
get ":id/issues" do get ":id/issues" do
validate_anonymous_search_access! if declared_params[:search].present?
issues = paginate(find_issues(project_id: user_project.id)) issues = paginate(find_issues(project_id: user_project.id))
options = { options = {
...@@ -222,6 +228,8 @@ module API ...@@ -222,6 +228,8 @@ module API
use :issues_stats_params use :issues_stats_params
end end
get ":id/issues_statistics" do get ":id/issues_statistics" do
validate_anonymous_search_access! if declared_params[:search].present?
present issues_statistics(project_id: user_project.id), with: Grape::Presenters::Presenter present issues_statistics(project_id: user_project.id), with: Grape::Presenters::Presenter
end end
......
...@@ -136,6 +136,7 @@ module API ...@@ -136,6 +136,7 @@ module API
end end
get feature_category: :code_review do get feature_category: :code_review do
authenticate! unless params[:scope] == 'all' authenticate! unless params[:scope] == 'all'
validate_anonymous_search_access! if params[:search].present?
merge_requests = find_merge_requests merge_requests = find_merge_requests
present merge_requests, serializer_options_for(merge_requests) present merge_requests, serializer_options_for(merge_requests)
...@@ -155,6 +156,7 @@ module API ...@@ -155,6 +156,7 @@ module API
default: true default: true
end end
get ":id/merge_requests", feature_category: :code_review do get ":id/merge_requests", feature_category: :code_review do
validate_anonymous_search_access! if declared_params[:search].present?
merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true) merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true)
present merge_requests, serializer_options_for(merge_requests).merge(group: user_group) present merge_requests, serializer_options_for(merge_requests).merge(group: user_group)
...@@ -195,6 +197,7 @@ module API ...@@ -195,6 +197,7 @@ module API
end end
get ":id/merge_requests", feature_category: :code_review do get ":id/merge_requests", feature_category: :code_review do
authorize! :read_merge_request, user_project authorize! :read_merge_request, user_project
validate_anonymous_search_access! if declared_params[:search].present?
merge_requests = find_merge_requests(project_id: user_project.id) merge_requests = find_merge_requests(project_id: user_project.id)
......
...@@ -236,6 +236,36 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -236,6 +236,36 @@ RSpec.describe Resolvers::IssuesResolver do
resolve_issues(search: 'foo') resolve_issues(search: 'foo')
end end
context 'with anonymous user' do
let_it_be(:public_project) { create(:project, :public) }
let_it_be(:public_issue) { create(:issue, project: public_project, title: 'Test issue') }
context 'with disable_anonymous_search enabled' do
before do
stub_feature_flags(disable_anonymous_search: true)
end
it 'returns an error' do
error_message = "User must be authenticated to include the `search` argument."
expect { resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil }) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, error_message)
end
end
context 'with disable_anonymous_search disabled' do
before do
stub_feature_flags(disable_anonymous_search: false)
end
it 'returns correct issues' do
expect(
resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil })
).to contain_exactly(public_issue)
end
end
end
end end
describe 'filters by negated params' do describe 'filters by negated params' do
......
...@@ -138,6 +138,12 @@ RSpec.describe API::Issues do ...@@ -138,6 +138,12 @@ RSpec.describe API::Issues do
expect(json_response).to be_an Array expect(json_response).to be_an Array
end end
it_behaves_like 'issuable anonymous search' do
let(:url) { '/issues' }
let(:issuable) { issue }
let(:result) { issuable.id }
end
it 'returns authentication error without any scope' do it 'returns authentication error without any scope' do
get api('/issues') get api('/issues')
...@@ -256,6 +262,38 @@ RSpec.describe API::Issues do ...@@ -256,6 +262,38 @@ RSpec.describe API::Issues do
it_behaves_like 'issues statistics' it_behaves_like 'issues statistics'
end end
context 'with search param' do
let(:params) { { scope: 'all', search: 'foo' } }
let(:counts) { { all: 1, closed: 0, opened: 1 } }
it_behaves_like 'issues statistics'
context 'with anonymous user' do
let(:user) { nil }
context 'with disable_anonymous_search disabled' do
before do
stub_feature_flags(disable_anonymous_search: false)
end
it_behaves_like 'issues statistics'
end
context 'with disable_anonymous_search enabled' do
before do
stub_feature_flags(disable_anonymous_search: true)
end
it 'returns a unprocessable entity 422' do
get api("/issues_statistics"), params: params
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to include('User must be authenticated to use search')
end
end
end
end
end end
end end
......
...@@ -49,6 +49,12 @@ RSpec.describe API::MergeRequests do ...@@ -49,6 +49,12 @@ RSpec.describe API::MergeRequests do
expect_successful_response_with_paginated_array expect_successful_response_with_paginated_array
end end
it_behaves_like 'issuable anonymous search' do
let(:url) { endpoint_path }
let(:issuable) { merge_request }
let(:result) { [merge_request_merged.id, merge_request_locked.id, merge_request_closed.id, merge_request.id] }
end
end end
context 'when authenticated' do context 'when authenticated' do
...@@ -613,6 +619,12 @@ RSpec.describe API::MergeRequests do ...@@ -613,6 +619,12 @@ RSpec.describe API::MergeRequests do
) )
end end
it_behaves_like 'issuable anonymous search' do
let(:url) { '/merge_requests' }
let(:issuable) { merge_request }
let(:result) { [merge_request_merged.id, merge_request_locked.id, merge_request_closed.id, merge_request.id] }
end
it "returns authentication error without any scope" do it "returns authentication error without any scope" do
get api("/merge_requests") get api("/merge_requests")
......
# frozen_string_literal: true
RSpec.shared_examples 'issuable anonymous search' do
context 'with anonymous user' do
context 'with disable_anonymous_search disabled' do
before do
stub_feature_flags(disable_anonymous_search: false)
end
it 'returns issuables matching given search string for title' do
get api(url), params: { scope: 'all', search: issuable.title }
expect_paginated_array_response(result)
end
it 'returns issuables matching given search string for description' do
get api(url), params: { scope: 'all', search: issuable.description }
expect_paginated_array_response(result)
end
end
context 'with disable_anonymous_search enabled' do
before do
stub_feature_flags(disable_anonymous_search: true)
end
it "returns 422 error" do
get api(url), params: { scope: 'all', search: issuable.title }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to eq('User must be authenticated to use search')
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment