summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-31 09:55:45 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-31 09:55:45 +0000
commit8a97772aca25cb233778ce99c487c674c86ba2fd (patch)
tree4bbb2614385ca3cf88c166a5faaf0679cf97f7db
parent5bc8aa03f1ac7ff5a20ae77e8f62631370a52d1a (diff)
downloadgitlab-ce-8a97772aca25cb233778ce99c487c674c86ba2fd.tar.gz
Add latest changes from gitlab-org/security/gitlab@12-6-stable-ee
-rw-r--r--app/controllers/profiles/notifications_controller.rb1
-rw-r--r--app/controllers/projects/releases_controller.rb7
-rw-r--r--app/helpers/notifications_helper.rb4
-rw-r--r--app/models/evidence.rb15
-rw-r--r--app/policies/release_policy.rb27
-rw-r--r--app/views/sent_notifications/unsubscribe.html.haml7
-rw-r--r--changelogs/unreleased/security-11-graphql-timeout.yml5
-rw-r--r--changelogs/unreleased/security-29983-private-project-name-exposed.yml5
-rw-r--r--changelogs/unreleased/security-34072-project-name-disclosed.yml5
-rw-r--r--changelogs/unreleased/security-fix-invalid-byte-sequence-upload-links-master.yml5
-rw-r--r--changelogs/unreleased/security-vulnerable-evidence-12-7.yml5
-rw-r--r--config/initializers/graphql.rb4
-rw-r--r--lib/api/entities.rb6
-rw-r--r--lib/banzai/filter/relative_link_filter.rb12
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/controllers/profiles/notifications_controller_spec.rb29
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb81
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb35
-rw-r--r--spec/fixtures/api/schemas/evidences/milestone.json9
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb9
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb12
-rw-r--r--spec/requests/api/releases_spec.rb34
22 files changed, 296 insertions, 24 deletions
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index 5f44e55f3ef..d295b64082c 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -11,6 +11,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController
exclude_group_ids: @group_notifications.select(:source_id)
).execute.map { |group| current_user.notification_settings_for(group, inherit: true) }
@project_notifications = current_user.notification_settings.for_projects.order(:id)
+ .select { |notification| current_user.can?(:read_project, notification.source) }
@global_notification_setting = current_user.global_notification_setting
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index ffe69fe97e4..d6030a9e455 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -10,7 +10,7 @@ class Projects::ReleasesController < Projects::ApplicationController
push_frontend_feature_flag(:release_evidence_collection, project)
end
before_action :authorize_update_release!, only: %i[edit update]
- before_action :authorize_download_code!, only: [:evidence]
+ before_action :authorize_read_release_evidence!, only: [:evidence]
def index
respond_to do |format|
@@ -47,6 +47,11 @@ class Projects::ReleasesController < Projects::ApplicationController
access_denied! unless can?(current_user, :update_release, release)
end
+ def authorize_read_release_evidence!
+ access_denied! unless Feature.enabled?(:release_evidence, project, default_enabled: true)
+ access_denied! unless can?(current_user, :read_release_evidence, release)
+ end
+
def release
@release ||= project.releases.find_by_tag!(sanitized_tag_name)
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 8855e0cdd70..9a64fe98f86 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -116,4 +116,8 @@ module NotificationsHelper
def show_unsubscribe_title?(noteable)
can?(current_user, "read_#{noteable.to_ability_name}".to_sym, noteable)
end
+
+ def can_read_project?(project)
+ can?(current_user, :read_project, project)
+ end
end
diff --git a/app/models/evidence.rb b/app/models/evidence.rb
index 69a00f1cb3f..55149ab0dfa 100644
--- a/app/models/evidence.rb
+++ b/app/models/evidence.rb
@@ -15,6 +15,21 @@ class Evidence < ApplicationRecord
@milestones ||= release.milestones.includes(:issues)
end
+ ##
+ # Return `summary` without sensitive information.
+ #
+ # Removing issues from summary in order to prevent leaking confidential ones.
+ # See more https://gitlab.com/gitlab-org/gitlab/issues/121930
+ def summary
+ safe_summary = read_attribute(:summary)
+
+ safe_summary.dig('release', 'milestones')&.each do |milestone|
+ milestone.delete('issues')
+ end
+
+ safe_summary
+ end
+
private
def generate_summary_and_sha
diff --git a/app/policies/release_policy.rb b/app/policies/release_policy.rb
index d7f9e5d7445..0fd1312c511 100644
--- a/app/policies/release_policy.rb
+++ b/app/policies/release_policy.rb
@@ -2,4 +2,31 @@
class ReleasePolicy < BasePolicy
delegate { @subject.project }
+
+ rule { allowed_to_read_evidence & external_authorization_service_disabled }.policy do
+ enable :read_release_evidence
+ end
+
+ ##
+ # evidence.summary includes the following entities:
+ # - Release
+ # - git-tag (Repository)
+ # - Project
+ # - Milestones
+ # - Issues
+ condition(:allowed_to_read_evidence) do
+ can?(:read_release) &&
+ can?(:download_code) &&
+ can?(:read_project) &&
+ can?(:read_milestone) &&
+ can?(:read_issue)
+ end
+
+ ##
+ # Currently, we don't support release evidence for the GitLab instances
+ # that enables external authorization services.
+ # See https://gitlab.com/gitlab-org/gitlab/issues/121930.
+ condition(:external_authorization_service_disabled) do
+ !Gitlab::ExternalAuthorization::Config.enabled?
+ end
end
diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml
index 22fcfcda297..1eecbe3bc0e 100644
--- a/app/views/sent_notifications/unsubscribe.html.haml
+++ b/app/views/sent_notifications/unsubscribe.html.haml
@@ -1,13 +1,16 @@
- noteable = @sent_notification.noteable
- noteable_type = @sent_notification.noteable_type.titleize.downcase
- noteable_text = show_unsubscribe_title?(noteable) ? %(#{noteable.title} (#{noteable.to_reference})) : %(#{noteable.to_reference})
-- page_title _("Unsubscribe"), noteable_text, noteable_type.pluralize, @sent_notification.project.full_name
+- show_project_path = can_read_project?(@sent_notification.project)
+- project_path = show_project_path ? @sent_notification.project.full_name : _("GitLab / Unsubscribe")
+- noteable_url = show_project_path ? url_for([@sent_notification.project.namespace.becomes(Namespace), @sent_notification.project, noteable]) : breadcrumb_title_link
+- page_title _('Unsubscribe'), noteable_text, noteable_type.pluralize, project_path
%h3.page-title
= _("Unsubscribe from %{type}") % { type: noteable_type }
%p
- - link_to_noteable_text = link_to(noteable_text, url_for([@sent_notification.project.namespace.becomes(Namespace), @sent_notification.project, noteable]))
+ - link_to_noteable_text = link_to(noteable_text, noteable_url)
= _("Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?").html_safe % { type: noteable_type, link_to_noteable_text: link_to_noteable_text }
%p
diff --git a/changelogs/unreleased/security-11-graphql-timeout.yml b/changelogs/unreleased/security-11-graphql-timeout.yml
new file mode 100644
index 00000000000..1d06aaced9d
--- /dev/null
+++ b/changelogs/unreleased/security-11-graphql-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: 'GraphQL: Add timeout to all queries'
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-29983-private-project-name-exposed.yml b/changelogs/unreleased/security-29983-private-project-name-exposed.yml
new file mode 100644
index 00000000000..2cae417ec1d
--- /dev/null
+++ b/changelogs/unreleased/security-29983-private-project-name-exposed.yml
@@ -0,0 +1,5 @@
+---
+title: Filter out notification settings for projects that a user does not have at least read access
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-34072-project-name-disclosed.yml b/changelogs/unreleased/security-34072-project-name-disclosed.yml
new file mode 100644
index 00000000000..f14c7728273
--- /dev/null
+++ b/changelogs/unreleased/security-34072-project-name-disclosed.yml
@@ -0,0 +1,5 @@
+---
+title: Hide project name and path when unsusbcribing from an issue or merge request
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-invalid-byte-sequence-upload-links-master.yml b/changelogs/unreleased/security-fix-invalid-byte-sequence-upload-links-master.yml
new file mode 100644
index 00000000000..afe48b448b0
--- /dev/null
+++ b/changelogs/unreleased/security-fix-invalid-byte-sequence-upload-links-master.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 error caused by invalid byte sequences in uploads links
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-vulnerable-evidence-12-7.yml b/changelogs/unreleased/security-vulnerable-evidence-12-7.yml
new file mode 100644
index 00000000000..9a7cdeeddb4
--- /dev/null
+++ b/changelogs/unreleased/security-vulnerable-evidence-12-7.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Vulnerability of Release Evidence
+merge_request:
+author:
+type: security
diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb
index f1bc289f1f0..2b21c9d9729 100644
--- a/config/initializers/graphql.rb
+++ b/config/initializers/graphql.rb
@@ -5,3 +5,7 @@ GraphQL::Field.accepts_definitions(authorize: GraphQL::Define.assign_metadata_ke
GraphQL::Schema::Object.accepts_definition(:authorize)
GraphQL::Schema::Field.accepts_definition(:authorize)
+
+GitlabSchema.middleware << GraphQL::Schema::TimeoutMiddleware.new(max_seconds: ENV.fetch('GITLAB_RAILS_GRAPHQL_TIMEOUT', 30).to_i) do |timeout_error, query|
+ Gitlab::GraphqlLogger.error(message: timeout_error.to_s, query: query.query_string, query_variables: query.provided_variables)
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index cc95be5e3be..76963777566 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1336,7 +1336,7 @@ module API
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? }
expose :upcoming_release?, as: :upcoming_release
- expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? }
+ expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? && can_read_milestone? }
expose :commit_path, expose_nil: false
expose :tag_path, expose_nil: false
expose :evidence_sha, expose_nil: false, if: ->(_, _) { can_download_code? }
@@ -1362,6 +1362,10 @@ module API
def can_download_code?
Ability.allowed?(options[:current_user], :download_code, object.project)
end
+
+ def can_read_milestone?
+ Ability.allowed?(options[:current_user], :read_milestone, object.project)
+ end
end
class Tag < Grape::Entity
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 583b0081319..4f257189f8e 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -116,7 +116,7 @@ module Banzai
end
def process_link_to_upload_attr(html_attr)
- path_parts = [Addressable::URI.unescape(html_attr.value)]
+ path_parts = [unescape_and_scrub_uri(html_attr.value)]
if project
path_parts.unshift(relative_url_root, project.full_path)
@@ -172,7 +172,7 @@ module Banzai
end
def cleaned_file_path(uri)
- Addressable::URI.unescape(uri.path).scrub.delete("\0").chomp("/")
+ unescape_and_scrub_uri(uri.path).delete("\0").chomp("/")
end
def relative_file_path(uri)
@@ -184,7 +184,7 @@ module Banzai
def request_path
return unless context[:requested_path]
- Addressable::URI.unescape(context[:requested_path]).chomp("/")
+ unescape_and_scrub_uri(context[:requested_path]).chomp("/")
end
# Convert a relative path into its correct location based on the currently
@@ -266,6 +266,12 @@ module Banzai
def repository
@repository ||= project&.repository
end
+
+ private
+
+ def unescape_and_scrub_uri(uri)
+ Addressable::URI.unescape(uri).scrub
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9044dcc2ff8..40d5e7223d6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8509,6 +8509,9 @@ msgstr ""
msgid "GitHub import"
msgstr ""
+msgid "GitLab / Unsubscribe"
+msgstr ""
+
msgid "GitLab CI Linter has been moved"
msgstr ""
diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb
index dbc408bcdd9..ede68744ac6 100644
--- a/spec/controllers/profiles/notifications_controller_spec.rb
+++ b/spec/controllers/profiles/notifications_controller_spec.rb
@@ -52,6 +52,35 @@ describe Profiles::NotificationsController do
end.to exceed_query_limit(control)
end
end
+
+ context 'with project notifications' do
+ let!(:notification_setting) { create(:notification_setting, source: project, user: user, level: :watch) }
+
+ before do
+ sign_in(user)
+ get :show
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :public) }
+
+ it 'shows notification setting for project' do
+ expect(assigns(:project_notifications).map(&:source_id)).to include(project.id)
+ end
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :private) }
+
+ it 'shows notification setting for project' do
+ # notification settings for given project were created before project was set to private
+ expect(user.notification_settings.for_projects.map(&:source_id)).to include(project.id)
+
+ # check that notification settings for project where user does not have access are filtered
+ expect(assigns(:project_notifications)).to be_empty
+ end
+ end
+ end
end
describe 'POST update' do
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index e9fa3764117..750e9aabef0 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -167,7 +167,7 @@ describe Projects::ReleasesController do
end
describe 'GET #evidence' do
- let(:tag_name) { "v1.1.0-evidence" }
+ let_it_be(:tag_name) { "v1.1.0-evidence" }
let!(:release) { create(:release, :with_evidence, project: project, tag: tag_name) }
let(:tag) { CGI.escape(release.tag) }
let(:format) { :json }
@@ -220,6 +220,85 @@ describe Projects::ReleasesController do
it_behaves_like 'successful request'
end
end
+
+ context 'when release is associated to a milestone which includes an issue' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:milestone) { create(:milestone, project: project, issues: [issue]) }
+ let_it_be(:release) { create(:release, project: project, tag: tag_name, milestones: [milestone]) }
+
+ before do
+ create(:evidence, release: release)
+ end
+
+ shared_examples_for 'does not show the issue in evidence' do
+ it do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['release']['milestones']
+ .all? { |milestone| milestone['issues'].nil? }).to eq(true)
+ end
+ end
+
+ shared_examples_for 'evidence not found' do
+ it do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples_for 'safely expose evidence' do
+ it_behaves_like 'does not show the issue in evidence'
+
+ context 'when the issue is confidential' do
+ let(:issue) { create(:issue, :confidential, project: project) }
+
+ it_behaves_like 'does not show the issue in evidence'
+ end
+
+ context 'when the user is the author of the confidential issue' do
+ let(:issue) { create(:issue, :confidential, project: project, author: user) }
+
+ it_behaves_like 'does not show the issue in evidence'
+ end
+
+ context 'when project is private' do
+ let!(:project) { create(:project, :repository, :private) }
+
+ it_behaves_like 'evidence not found'
+ end
+
+ context 'when project restricts the visibility of issues to project members only' do
+ let!(:project) { create(:project, :repository, :issues_private) }
+
+ it_behaves_like 'evidence not found'
+ end
+ end
+
+ context 'when user is non-project member' do
+ let(:user) { create(:user) }
+
+ it_behaves_like 'safely expose evidence'
+ end
+
+ context 'when user is auditor', if: Gitlab.ee? do
+ let(:user) { create(:user, :auditor) }
+
+ it_behaves_like 'safely expose evidence'
+ end
+
+ context 'when external authorization control is enabled' do
+ let(:user) { create(:user) }
+
+ before do
+ stub_application_setting(external_authorization_service_enabled: true)
+ end
+
+ it_behaves_like 'evidence not found'
+ end
+ end
end
private
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 0e634d8ba99..4dd4f49dcf1 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -56,7 +56,7 @@ describe SentNotificationsController do
get(:unsubscribe, params: { id: sent_notification.reply_key })
end
- shared_examples 'unsubscribing as anonymous' do
+ shared_examples 'unsubscribing as anonymous' do |project_visibility|
it 'does not unsubscribe the user' do
expect(noteable.subscribed?(user, target_project)).to be_truthy
end
@@ -69,6 +69,18 @@ describe SentNotificationsController do
expect(response.status).to eq(200)
expect(response).to render_template :unsubscribe
end
+
+ if project_visibility == :private
+ it 'does not show project name or path' do
+ expect(response.body).not_to include(noteable.project.name)
+ expect(response.body).not_to include(noteable.project.full_name)
+ end
+ else
+ it 'shows project name or path' do
+ expect(response.body).to include(noteable.project.name)
+ expect(response.body).to include(noteable.project.full_name)
+ end
+ end
end
context 'when project is public' do
@@ -79,7 +91,7 @@ describe SentNotificationsController do
expect(response.body).to include(issue.title)
end
- it_behaves_like 'unsubscribing as anonymous'
+ it_behaves_like 'unsubscribing as anonymous', :public
end
context 'when unsubscribing from confidential issue' do
@@ -90,7 +102,7 @@ describe SentNotificationsController do
expect(response.body).to include(confidential_issue.to_reference)
end
- it_behaves_like 'unsubscribing as anonymous'
+ it_behaves_like 'unsubscribing as anonymous', :public
end
context 'when unsubscribing from merge request' do
@@ -100,7 +112,12 @@ describe SentNotificationsController do
expect(response.body).to include(merge_request.title)
end
- it_behaves_like 'unsubscribing as anonymous'
+ it 'shows project name or path' do
+ expect(response.body).to include(issue.project.name)
+ expect(response.body).to include(issue.project.full_name)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous', :public
end
end
@@ -110,11 +127,11 @@ describe SentNotificationsController do
context 'when unsubscribing from issue' do
let(:noteable) { issue }
- it 'shows issue title' do
+ it 'does not show issue title' do
expect(response.body).not_to include(issue.title)
end
- it_behaves_like 'unsubscribing as anonymous'
+ it_behaves_like 'unsubscribing as anonymous', :private
end
context 'when unsubscribing from confidential issue' do
@@ -125,17 +142,17 @@ describe SentNotificationsController do
expect(response.body).to include(confidential_issue.to_reference)
end
- it_behaves_like 'unsubscribing as anonymous'
+ it_behaves_like 'unsubscribing as anonymous', :private
end
context 'when unsubscribing from merge request' do
let(:noteable) { merge_request }
- it 'shows merge request title' do
+ it 'dos not show merge request title' do
expect(response.body).not_to include(merge_request.title)
end
- it_behaves_like 'unsubscribing as anonymous'
+ it_behaves_like 'unsubscribing as anonymous', :private
end
end
end
diff --git a/spec/fixtures/api/schemas/evidences/milestone.json b/spec/fixtures/api/schemas/evidences/milestone.json
index ab27fdecde2..3ce0644225b 100644
--- a/spec/fixtures/api/schemas/evidences/milestone.json
+++ b/spec/fixtures/api/schemas/evidences/milestone.json
@@ -7,8 +7,7 @@
"state",
"iid",
"created_at",
- "due_date",
- "issues"
+ "due_date"
],
"properties": {
"id": { "type": "integer" },
@@ -17,11 +16,7 @@
"state": { "type": "string" },
"iid": { "type": "integer" },
"created_at": { "type": "date" },
- "due_date": { "type": ["date", "null"] },
- "issues": {
- "type": "array",
- "items": { "$ref": "issue.json" }
- }
+ "due_date": { "type": ["date", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index a17a645d4d0..9f467d7a6fd 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -128,6 +128,15 @@ describe Banzai::Filter::RelativeLinkFilter do
expect { filter(act) }.not_to raise_error
end
+ it 'does not raise an exception on URIs containing invalid utf-8 byte sequences in uploads' do
+ act = link("/uploads/%FF")
+ expect { filter(act) }.not_to raise_error
+ end
+
+ it 'does not raise an exception on URIs containing invalid utf-8 byte sequences in context requested path' do
+ expect { filter(link("files/test.md"), requested_path: '%FF') }.not_to raise_error
+ end
+
it 'does not raise an exception with a garbled path' do
act = link("open(/var/tmp/):%20/location%0Afrom:%20/test")
expect { filter(act) }.not_to raise_error
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index 2aeb75a10b4..2cb8436662b 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -8,6 +8,18 @@ describe 'GitlabSchema configurations' do
set(:project) { create(:project) }
shared_examples 'imposing query limits' do
+ describe 'timeouts' do
+ context 'when timeout is reached' do
+ it 'shows an error' do
+ Timecop.scale(50000000) do # ludicrously large number because the timeout has to happen before the query even begins
+ subject
+
+ expect_graphql_errors_to_include /Timeout/
+ end
+ end
+ end
+ end
+
describe '#max_complexity' do
context 'when complexity is too high' do
it 'shows an error' do
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index da04e852795..233f0497b7f 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -340,6 +340,40 @@ describe API::Releases do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'when release is associated to a milestone' do
+ let!(:release) do
+ create(:release, tag: 'v0.1', project: project, milestones: [milestone])
+ end
+
+ let(:milestone) { create(:milestone, project: project) }
+
+ it 'exposes milestones' do
+ get api("/projects/#{project.id}/releases/v0.1", non_project_member)
+
+ expect(json_response['milestones'].first['title']).to eq(milestone.title)
+ end
+
+ context 'when project restricts visibility of issues and merge requests' do
+ let!(:project) { create(:project, :repository, :public, :issues_private, :merge_requests_private) }
+
+ it 'does not expose milestones' do
+ get api("/projects/#{project.id}/releases/v0.1", non_project_member)
+
+ expect(json_response['milestones']).to be_nil
+ end
+ end
+
+ context 'when project restricts visibility of issues' do
+ let!(:project) { create(:project, :repository, :public, :issues_private) }
+
+ it 'exposes milestones' do
+ get api("/projects/#{project.id}/releases/v0.1", non_project_member)
+
+ expect(json_response['milestones'].first['title']).to eq(milestone.title)
+ end
+ end
+ end
end
end
end