diff options
author | GitLab Release Tools Bot <delivery-team+release-tools@gitlab.com> | 2021-04-28 07:28:43 +0000 |
---|---|---|
committer | GitLab Release Tools Bot <delivery-team+release-tools@gitlab.com> | 2021-04-28 07:28:43 +0000 |
commit | bbdea2d94d2d87b366fe8e023dcc76c78cfe2375 (patch) | |
tree | b9a1c84456ffdd0a9671d5eba849866b2f5ca09a | |
parent | 2fad41087674984a064cf6a312ac34c16bb2a1aa (diff) | |
parent | d8d57a90208c62b29e5218f97525404859232a55 (diff) | |
download | gitlab-ce-bbdea2d94d2d87b366fe8e023dcc76c78cfe2375.tar.gz |
Merge remote-tracking branch 'dev/13-11-stable' into 13-11-stable
26 files changed, 216 insertions, 49 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 096a598100e..a48f0afd15b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 13.11.2 (2021-04-27) + +### Security (5 changes) + +- Prevent tokens with only read_api scope from executing mutations. +- Do not allow deploy tokens in the dependency proxy authentication service. +- Disable keyset pagination for branches by default. +- Bump Carrierwave gem to v1.3.2. +- Restrict setting system_note_timestamp to owners. + + ## 13.11.1 (2021-04-22) ### Changed (1 change) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 9c43b91bbd2..07ae670e11e 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -13.11.1
\ No newline at end of file +13.11.2
\ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 19fad573b63..007ca87b465 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -171,10 +171,11 @@ GEM capybara-screenshot (1.0.22) capybara (>= 1.0, < 4) launchy - carrierwave (1.3.1) + carrierwave (1.3.2) activemodel (>= 4.0.0) activesupport (>= 4.0.0) mime-types (>= 1.16) + ssrf_filter (~> 1.0) cbor (0.5.9.6) character_set (1.4.0) charlock_holmes (0.7.7) @@ -1205,6 +1206,7 @@ GEM sprockets (>= 3.0.0) sqlite3 (1.3.13) sshkey (2.0.0) + ssrf_filter (1.0.7) stackprof (0.2.15) state_machines (0.5.0) state_machines-activemodel (0.8.0) @@ -1 +1 @@ -13.11.1
\ No newline at end of file +13.11.2
\ No newline at end of file diff --git a/app/controllers/concerns/sessionless_authentication.rb b/app/controllers/concerns/sessionless_authentication.rb index 882fef7a342..3c8a683439a 100644 --- a/app/controllers/concerns/sessionless_authentication.rb +++ b/app/controllers/concerns/sessionless_authentication.rb @@ -7,11 +7,15 @@ module SessionlessAuthentication # This filter handles personal access tokens, atom requests with rss tokens, and static object tokens def authenticate_sessionless_user!(request_format) - user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user(request_format) + user = request_authenticator.find_sessionless_user(request_format) sessionless_sign_in(user) if user end + def request_authenticator + @request_authenticator ||= Gitlab::Auth::RequestAuthenticator.new(request) + end + def sessionless_user? current_user && !session.key?('warden.user.user.key') end diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index a13ec1daddb..38bfb5ef2f8 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -110,7 +110,13 @@ class GraphqlController < ApplicationController end def context - @context ||= { current_user: current_user, is_sessionless_user: !!sessionless_user?, request: request } + api_user = !!sessionless_user? + @context ||= { + current_user: current_user, + is_sessionless_user: api_user, + request: request, + scope_validator: ::Gitlab::Auth::ScopeValidator.new(api_user, request_authenticator) + } end def build_variables(variable_info) diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index f522dffdf3e..5006aa75ce5 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -185,7 +185,7 @@ class Projects::BranchesController < Projects::ApplicationController # Here we get one more branch to indicate if there are more data we're not showing limit = @overview_max_branches + 1 - if Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: true) + if Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: :yaml) @active_branches = BranchesFinder.new(@repository, { per_page: limit, sort: sort_value_recently_updated }) .execute(gitaly_pagination: true).select(&:active?) diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 1f18a37fcb9..da658e1f108 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -44,9 +44,18 @@ module Mutations end end + def self.authorizes_object? + true + end + def self.authorized?(object, context) - # we never provide an object to mutations, but we do need to have a user. - context[:current_user].present? && !context[:current_user].blocked? + auth = ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(:execute_graphql_mutation, :api) + + return true if auth.ok?(:global, context[:current_user], + scope_validator: context[:scope_validator]) + + # in our mutations we raise, rather than returning a null value. + raise_resource_not_available_error! end # See: AuthorizeResource#authorized_resource? diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 5ee34ebbb2f..d16c4734b2c 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -23,6 +23,7 @@ class GlobalPolicy < BasePolicy prevent :receive_notifications prevent :use_quick_actions prevent :create_group + prevent :execute_graphql_mutation end rule { default }.policy do @@ -32,6 +33,7 @@ class GlobalPolicy < BasePolicy enable :receive_notifications enable :use_quick_actions enable :use_slash_commands + enable :execute_graphql_mutation end rule { inactive }.policy do @@ -48,6 +50,8 @@ class GlobalPolicy < BasePolicy prevent :use_slash_commands end + rule { ~can?(:access_api) }.prevent :execute_graphql_mutation + rule { blocked | (internal & ~migration_bot & ~security_bot) }.policy do prevent :access_git end diff --git a/app/services/auth/dependency_proxy_authentication_service.rb b/app/services/auth/dependency_proxy_authentication_service.rb index 1b8c16b7c79..fab42e0ebb6 100644 --- a/app/services/auth/dependency_proxy_authentication_service.rb +++ b/app/services/auth/dependency_proxy_authentication_service.rb @@ -8,7 +8,10 @@ module Auth def execute(authentication_abilities:) return error('dependency proxy not enabled', 404) unless ::Gitlab.config.dependency_proxy.enabled - return error('access forbidden', 403) unless current_user + + # Because app/controllers/concerns/dependency_proxy/auth.rb consumes this + # JWT only as `User.find`, we currently only allow User (not DeployToken, etc) + return error('access forbidden', 403) unless current_user.is_a?(User) { token: authorized_token.encoded } end diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 87615d1b4f2..07e4a10708e 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -34,7 +34,7 @@ module Issues private - def filter_params(merge_request) + def filter_params(issue) super moved_issue = params.delete(:moved_issue) @@ -44,6 +44,8 @@ module Issues params.delete(:iid) unless current_user.can?(:set_issue_iid, project) params.delete(:created_at) unless moved_issue || current_user.can?(:set_issue_created_at, project) params.delete(:updated_at) unless moved_issue || current_user.can?(:set_issue_updated_at, project) + + issue.system_note_timestamp = params[:created_at] || params[:updated_at] end def create_assignee_note(issue, old_assignees) diff --git a/app/services/projects/branches_by_mode_service.rb b/app/services/projects/branches_by_mode_service.rb index dbdcef066f4..090671cc79a 100644 --- a/app/services/projects/branches_by_mode_service.rb +++ b/app/services/projects/branches_by_mode_service.rb @@ -37,7 +37,7 @@ class Projects::BranchesByModeService def use_gitaly_pagination? return false if params[:page].present? || params[:search].present? - Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: true) + Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: :yaml) end def fetch_branches_via_offset_pagination diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml index 94f8703657b..5f31ec4087e 100644 --- a/app/views/projects/mirrors/_authentication_method.html.haml +++ b/app/views/projects/mirrors/_authentication_method.html.haml @@ -13,4 +13,4 @@ .form-group .well-password-auth.collapse.js-well-password-auth = f.label :password, _("Password"), class: "label-bold" - = f.password_field :password, value: mirror.password, class: 'form-control gl-form-input qa-password', autocomplete: 'new-password' + = f.password_field :password, class: 'form-control gl-form-input qa-password', autocomplete: 'new-password' diff --git a/config/feature_flags/development/branch_list_keyset_pagination.yml b/config/feature_flags/development/branch_list_keyset_pagination.yml index 23b573e5004..12200292058 100644 --- a/config/feature_flags/development/branch_list_keyset_pagination.yml +++ b/config/feature_flags/development/branch_list_keyset_pagination.yml @@ -5,4 +5,4 @@ rollout_issue_url: milestone: '13.2' type: development group: group::source code -default_enabled: true +default_enabled: false diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4f2ac73c0d3..c844655f0b3 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -249,7 +249,6 @@ module API authorize! :create_issue, user_project issue_params = declared_params(include_missing: false) - issue_params[:system_note_timestamp] = params[:created_at] issue_params = convert_parameters_from_legacy_format(issue_params) @@ -293,8 +292,6 @@ module API issue = user_project.issues.find_by!(iid: params.delete(:issue_iid)) authorize! :update_issue, issue - issue.system_note_timestamp = params[:updated_at] - update_params = declared_params(include_missing: false).merge(request: request, api: true) update_params = convert_parameters_from_legacy_format(update_params) diff --git a/lib/gitlab/auth/scope_validator.rb b/lib/gitlab/auth/scope_validator.rb new file mode 100644 index 00000000000..de4c36ad594 --- /dev/null +++ b/lib/gitlab/auth/scope_validator.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Wrapper around a RequestAuthenticator to +# perform authorization of scopes. Access is limited to +# only those methods needed to validate that an API user +# has at least one permitted scope. +module Gitlab + module Auth + class ScopeValidator + def initialize(api_user, request_authenticator) + @api_user = api_user + @request_authenticator = request_authenticator + end + + def valid_for?(permitted) + return true unless @api_user + return true if permitted.none? + + scopes = permitted.map { |s| API::Scope.new(s) } + @request_authenticator.valid_access_token?(scopes: scopes) + end + end + end +end diff --git a/lib/gitlab/graphql/authorize/object_authorization.rb b/lib/gitlab/graphql/authorize/object_authorization.rb index 0bc87108871..f13acc9ea27 100644 --- a/lib/gitlab/graphql/authorize/object_authorization.rb +++ b/lib/gitlab/graphql/authorize/object_authorization.rb @@ -4,10 +4,11 @@ module Gitlab module Graphql module Authorize class ObjectAuthorization - attr_reader :abilities + attr_reader :abilities, :permitted_scopes - def initialize(abilities) + def initialize(abilities, scopes = %i[api read_api]) @abilities = Array.wrap(abilities).flatten + @permitted_scopes = Array.wrap(scopes) end def none? @@ -18,7 +19,13 @@ module Gitlab abilities.present? end - def ok?(object, current_user) + def ok?(object, current_user, scope_validator: nil) + scopes_ok?(scope_validator) && abilities_ok?(object, current_user) + end + + private + + def abilities_ok?(object, current_user) return true if none? subject = object.try(:declarative_policy_subject) || object @@ -26,6 +33,12 @@ module Gitlab Ability.allowed?(current_user, ability, subject) end end + + def scopes_ok?(validator) + return true unless validator.present? + + validator.valid_for?(permitted_scopes) + end end end end diff --git a/lib/gitlab/pagination/gitaly_keyset_pager.rb b/lib/gitlab/pagination/gitaly_keyset_pager.rb index 1350168967e..b05891066ac 100644 --- a/lib/gitlab/pagination/gitaly_keyset_pager.rb +++ b/lib/gitlab/pagination/gitaly_keyset_pager.rb @@ -26,11 +26,11 @@ module Gitlab private def keyset_pagination_enabled? - Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: true) && params[:pagination] == 'keyset' + Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: :yaml) && params[:pagination] == 'keyset' end def paginate_first_page? - Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: true) && (params[:page].blank? || params[:page].to_i == 1) + Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: :yaml) && (params[:page].blank? || params[:page].to_i == 1) end def paginate_via_gitaly(finder) diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb index 1eed1c8e2ae..8dd8ed361ba 100644 --- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb @@ -31,6 +31,8 @@ RSpec.describe 'Adding a Note' do project.add_developer(current_user) end + it_behaves_like 'a working GraphQL mutation' + it_behaves_like 'a Note mutation that creates a Note' it_behaves_like 'a Note mutation when there are active record validation errors' diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 0fe68be027c..8f10de59526 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -943,6 +943,34 @@ RSpec.describe API::Issues do it_behaves_like 'issuable update endpoint' do let(:entity) { issue } end + + describe 'updated_at param' do + let(:fixed_time) { Time.new(2001, 1, 1) } + let(:updated_at) { Time.new(2000, 1, 1) } + + before do + travel_to fixed_time + end + + it 'allows admins to set the timestamp' do + put api("/projects/#{project.id}/issues/#{issue.iid}", admin), params: { labels: 'label1', updated_at: updated_at } + + expect(response).to have_gitlab_http_status(:ok) + expect(Time.parse(json_response['updated_at'])).to be_like_time(updated_at) + expect(ResourceLabelEvent.last.created_at).to be_like_time(updated_at) + end + + it 'does not allow other users to set the timestamp' do + reporter = create(:user) + project.add_developer(reporter) + + put api("/projects/#{project.id}/issues/#{issue.iid}", reporter), params: { labels: 'label1', updated_at: updated_at } + + expect(response).to have_gitlab_http_status(:ok) + expect(Time.parse(json_response['updated_at'])).to be_like_time(fixed_time) + expect(ResourceLabelEvent.last.created_at).to be_like_time(fixed_time) + end + end end describe 'DELETE /projects/:id/issues/:issue_iid' do diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb index 7f1db620d4f..9d3bd26a200 100644 --- a/spec/requests/api/issues/post_projects_issues_spec.rb +++ b/spec/requests/api/issues/post_projects_issues_spec.rb @@ -330,15 +330,21 @@ RSpec.describe API::Issues do end context 'setting created_at' do + let(:fixed_time) { Time.new(2001, 1, 1) } let(:creation_time) { 2.weeks.ago } let(:params) { { title: 'new issue', labels: 'label, label2', created_at: creation_time } } + before do + travel_to fixed_time + end + context 'by an admin' do it 'sets the creation time on the new issue' do post api("/projects/#{project.id}/issues", admin), params: params expect(response).to have_gitlab_http_status(:created) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) + expect(ResourceLabelEvent.last.created_at).to be_like_time(creation_time) end end @@ -348,6 +354,7 @@ RSpec.describe API::Issues do expect(response).to have_gitlab_http_status(:created) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) + expect(ResourceLabelEvent.last.created_at).to be_like_time(creation_time) end end @@ -356,19 +363,24 @@ RSpec.describe API::Issues do group = create(:group) group_project = create(:project, :public, namespace: group) group.add_owner(user2) + post api("/projects/#{group_project.id}/issues", user2), params: params expect(response).to have_gitlab_http_status(:created) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) + expect(ResourceLabelEvent.last.created_at).to be_like_time(creation_time) end end context 'by another user' do it 'ignores the given creation time' do + project.add_developer(user2) + post api("/projects/#{project.id}/issues", user2), params: params expect(response).to have_gitlab_http_status(:created) - expect(Time.parse(json_response['created_at'])).not_to be_like_time(creation_time) + expect(Time.parse(json_response['created_at'])).to be_like_time(fixed_time) + expect(ResourceLabelEvent.last.created_at).to be_like_time(fixed_time) end end end diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index 8be26784a3d..5b5658da97e 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -263,25 +263,21 @@ RSpec.describe JwtController do let(:credential_user) { group_deploy_token.username } let(:credential_password) { group_deploy_token.token } - it_behaves_like 'with valid credentials' + it_behaves_like 'returning response status', :forbidden end context 'with project deploy token' do let(:credential_user) { project_deploy_token.username } let(:credential_password) { project_deploy_token.token } - it_behaves_like 'with valid credentials' + it_behaves_like 'returning response status', :forbidden end context 'with invalid credentials' do let(:credential_user) { 'foo' } let(:credential_password) { 'bar' } - it 'returns unauthorized' do - subject - - expect(response).to have_gitlab_http_status(:unauthorized) - end + it_behaves_like 'returning response status', :unauthorized end end diff --git a/spec/services/auth/dependency_proxy_authentication_service_spec.rb b/spec/services/auth/dependency_proxy_authentication_service_spec.rb index ba50149f53a..1fd1677c7da 100644 --- a/spec/services/auth/dependency_proxy_authentication_service_spec.rb +++ b/spec/services/auth/dependency_proxy_authentication_service_spec.rb @@ -13,28 +13,31 @@ RSpec.describe Auth::DependencyProxyAuthenticationService do describe '#execute' do subject { service.execute(authentication_abilities: nil) } + shared_examples 'returning' do |status:, message:| + it "returns #{message}", :aggregate_failures do + expect(subject[:http_status]).to eq(status) + expect(subject[:message]).to eq(message) + end + end + context 'dependency proxy is not enabled' do before do stub_config(dependency_proxy: { enabled: false }) end - it 'returns not found' do - result = subject - - expect(result[:http_status]).to eq(404) - expect(result[:message]).to eq('dependency proxy not enabled') - end + it_behaves_like 'returning', status: 404, message: 'dependency proxy not enabled' end context 'without a user' do let(:user) { nil } - it 'returns forbidden' do - result = subject + it_behaves_like 'returning', status: 403, message: 'access forbidden' + end + + context 'with a deploy token as user' do + let_it_be(:user) { create(:deploy_token) } - expect(result[:http_status]).to eq(403) - expect(result[:message]).to eq('access forbidden') - end + it_behaves_like 'returning', status: 403, message: 'access forbidden' end context 'with a user' do diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index 0f743eaa7f5..7d4fce814f5 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -20,8 +20,9 @@ RSpec.describe Projects::DownloadService do context 'for URLs that are on the whitelist' do before do - stub_request(:get, 'http://mycompany.fogbugz.com/rails_sample.jpg').to_return(body: File.read(Rails.root + 'spec/fixtures/rails_sample.jpg')) - stub_request(:get, 'http://mycompany.fogbugz.com/doc_sample.txt').to_return(body: File.read(Rails.root + 'spec/fixtures/doc_sample.txt')) + # `ssrf_filter` resolves the hostname. See https://github.com/carrierwaveuploader/carrierwave/commit/91714adda998bc9e8decf5b1f5d260d808761304 + stub_request(:get, %r{http://[\d\.]+/rails_sample.jpg}).to_return(body: File.read(Rails.root + 'spec/fixtures/rails_sample.jpg')) + stub_request(:get, %r{http://[\d\.]+/doc_sample.txt}).to_return(body: File.read(Rails.root + 'spec/fixtures/doc_sample.txt')) end context 'an image file' do diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index d714f04fbba..9d6c6ab93e4 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -396,17 +396,21 @@ module GraphqlHelpers post api('/', current_user, version: 'graphql'), params: { _json: queries }, headers: headers end - def post_graphql(query, current_user: nil, variables: nil, headers: {}) + def post_graphql(query, current_user: nil, variables: nil, headers: {}, token: {}) params = { query: query, variables: serialize_variables(variables) } - post api('/', current_user, version: 'graphql'), params: params, headers: headers + post api('/', current_user, version: 'graphql', **token), params: params, headers: headers - if graphql_errors # Errors are acceptable, but not this one: - expect(graphql_errors).not_to include(a_hash_including('message' => 'Internal server error')) - end + return unless graphql_errors + + # Errors are acceptable, but not this one: + expect(graphql_errors).not_to include(a_hash_including('message' => 'Internal server error')) end - def post_graphql_mutation(mutation, current_user: nil) - post_graphql(mutation.query, current_user: current_user, variables: mutation.variables) + def post_graphql_mutation(mutation, current_user: nil, token: {}) + post_graphql(mutation.query, + current_user: current_user, + variables: mutation.variables, + token: token) end def post_graphql_mutation_with_uploads(mutation, current_user: nil) diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb index a66bc7112fe..d133c5ea641 100644 --- a/spec/support/shared_examples/requests/graphql_shared_examples.rb +++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb @@ -10,6 +10,52 @@ RSpec.shared_examples 'a working graphql query' do end end +RSpec.shared_examples 'a working GraphQL mutation' do + include GraphqlHelpers + + before do + post_graphql_mutation(mutation, current_user: current_user, token: token) + end + + shared_examples 'allows access to the mutation' do + let(:scopes) { ['api'] } + + it_behaves_like 'a working graphql query' do + it 'returns data' do + expect(graphql_data.compact).not_to be_empty + end + end + end + + shared_examples 'prevents access to the mutation' do + let(:scopes) { ['read_api'] } + + it 'does not resolve the mutation' do + expect(graphql_data.compact).to be_empty + expect(graphql_errors).to be_present + end + end + + context 'with a personal access token' do + let(:token) do + pat = create(:personal_access_token, user: current_user, scopes: scopes) + { personal_access_token: pat } + end + + it_behaves_like 'prevents access to the mutation' + it_behaves_like 'allows access to the mutation' + end + + context 'with an OAuth token' do + let(:token) do + { oauth_access_token: create(:oauth_access_token, resource_owner: current_user, scopes: scopes.join(' ')) } + end + + it_behaves_like 'prevents access to the mutation' + it_behaves_like 'allows access to the mutation' + end +end + RSpec.shared_examples 'a mutation on an unauthorized resource' do it_behaves_like 'a mutation that returns top-level errors', errors: [::Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] |