summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Release Tools Bot <delivery-team+release-tools@gitlab.com>2021-04-28 08:26:59 +0000
committerGitLab Release Tools Bot <delivery-team+release-tools@gitlab.com>2021-04-28 08:26:59 +0000
commit2e4f6a700ce74e8f08665fa7d450cec77099016c (patch)
tree56da6672965506b295210025da73ec1fb7faf2c6
parentde9c0f52604ccec94025c9b1d6904a272c41d783 (diff)
parent05f9b5a73c8c21cdf25a9d4ce984cc9bf21985c3 (diff)
downloadgitlab-ce-13-9-stable.tar.gz
Merge remote-tracking branch 'dev/13-9-stable' into 13-9-stable13-9-stable
-rw-r--r--CHANGELOG.md12
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile.lock4
-rw-r--r--VERSION2
-rw-r--r--app/controllers/concerns/sessionless_authentication.rb6
-rw-r--r--app/controllers/graphql_controller.rb8
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/graphql/mutations/base_mutation.rb4
-rw-r--r--app/policies/global_policy.rb4
-rw-r--r--app/services/auth/dependency_proxy_authentication_service.rb5
-rw-r--r--app/services/issues/base_service.rb4
-rw-r--r--app/services/projects/branches_by_mode_service.rb2
-rw-r--r--app/views/projects/mirrors/_authentication_method.html.haml2
-rw-r--r--config/feature_flags/development/branch_list_keyset_pagination.yml2
-rw-r--r--lib/api/issues.rb3
-rw-r--r--lib/gitlab/auth/scope_validator.rb24
-rw-r--r--lib/gitlab/graphql/authorize/object_authorization.rb45
-rw-r--r--lib/gitlab/pagination/gitaly_keyset_pager.rb4
-rw-r--r--package.json2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb2
-rw-r--r--spec/requests/api/issues/issues_spec.rb28
-rw-r--r--spec/requests/api/issues/post_projects_issues_spec.rb14
-rw-r--r--spec/requests/jwt_controller_spec.rb10
-rw-r--r--spec/services/auth/dependency_proxy_authentication_service_spec.rb25
-rw-r--r--spec/services/projects/download_service_spec.rb5
-rw-r--r--spec/support/helpers/graphql_helpers.rb18
-rw-r--r--spec/support/shared_examples/requests/graphql_shared_examples.rb46
-rw-r--r--yarn.lock8
28 files changed, 244 insertions, 49 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ac3dec21c8..892e8648b70 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 13.9.7 (2021-04-27)
+
+### Security (6 changes)
+
+- Prevent tokens with only read_api scope from executing mutations.
+- Update mermaid to version 8.9.2.
+- 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.9.6 (2021-04-13)
### Security (2 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index c2273bba244..1a88c9491f4 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-13.9.6 \ No newline at end of file
+13.9.7 \ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index 9d3fb1eee49..926549674f7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -170,10 +170,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)
@@ -1161,6 +1162,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)
diff --git a/VERSION b/VERSION
index c2273bba244..1a88c9491f4 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-13.9.6 \ No newline at end of file
+13.9.7 \ No newline at end of file
diff --git a/app/controllers/concerns/sessionless_authentication.rb b/app/controllers/concerns/sessionless_authentication.rb
index a9ef33bf3b9..36ba3d686af 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 152f07b4c16..8316e934f03 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -96,7 +96,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 6f3c96fa654..be1e932a1ab 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 ac5ddc5bd4c..a53cc72d904 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -28,8 +28,12 @@ module Mutations
end
def ready?(**args)
+ auth = ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(:execute_graphql_mutation, :api)
+
if Gitlab::Database.read_only?
raise Gitlab::Graphql::Errors::ResourceNotAvailable, ERROR_MESSAGE
+ elsif !auth.ok?(:global, current_user, scope_validator: context[:scope_validator])
+ raise_resource_not_available_error!
else
true
end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 9c79a797a6a..25893063238 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 25f319da03b..e621463d0db 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 fb66bfa073b..22a09a56cd0 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 ea09174f03a..fcbd453d402 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -236,7 +236,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)
@@ -280,8 +279,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
new file mode 100644
index 00000000000..f13acc9ea27
--- /dev/null
+++ b/lib/gitlab/graphql/authorize/object_authorization.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Authorize
+ class ObjectAuthorization
+ attr_reader :abilities, :permitted_scopes
+
+ def initialize(abilities, scopes = %i[api read_api])
+ @abilities = Array.wrap(abilities).flatten
+ @permitted_scopes = Array.wrap(scopes)
+ end
+
+ def none?
+ abilities.empty?
+ end
+
+ def any?
+ abilities.present?
+ end
+
+ 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
+ abilities.all? do |ability|
+ 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
+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/package.json b/package.json
index 66fc0668431..fa0d68ba7ba 100644
--- a/package.json
+++ b/package.json
@@ -106,7 +106,7 @@
"lodash": "^4.17.20",
"marked": "^0.3.12",
"mathjax": "3",
- "mermaid": "^8.9.0",
+ "mermaid": "^8.9.2",
"minimatch": "^3.0.4",
"monaco-editor": "^0.20.0",
"monaco-editor-webpack-plugin": "^1.9.0",
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 5b3e2363669..2536c5dd7c8 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 e154e691d5f..3087e0e1abb 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -262,25 +262,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 46d0c13dc18..20925a3316f 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -311,17 +311,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]
diff --git a/yarn.lock b/yarn.lock
index d3b92e7e2f3..3cbe49784bf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8200,10 +8200,10 @@ merge2@^1.2.3:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5"
integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==
-mermaid@^8.9.0:
- version "8.9.0"
- resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.9.0.tgz#e569517863ab903aa5389cd746b68ca958a8ca7c"
- integrity sha512-J582tyE1vkdNu4BGgfwXnFo4Mu6jpuc4uK96mIenavaak9kr4T5gaMmYCo/7edwq/vTBkx/soZ5LcJo5WXZ1BQ==
+mermaid@^8.9.2:
+ version "8.9.2"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.9.2.tgz#40bb2052cc6c4feaf5d93a5e527a8d06d0bacea7"
+ integrity sha512-XWEaraDRDlHZexdeHSSr/MH4VJAOksRSPudchi69ecZJ7IUjjlzHsg32n4ZwJUh6lFO+NMYLHwHNNYUyxIjGPg==
dependencies:
"@braintree/sanitize-url" "^3.1.0"
d3 "^5.7.0"