summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-09-29 12:52:49 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-29 12:52:49 +0000
commitdf13685041cc3f285d69efd6209cb33889dd8123 (patch)
treed759b911d6f38a8b5354ada89efae9d51079daed
parent25bed1192b485ee8a22d9c3a3b84e907375103c1 (diff)
downloadgitlab-ce-df13685041cc3f285d69efd6209cb33889dd8123.tar.gz
Add latest changes from gitlab-org/security/gitlab@14-2-stable-ee
-rw-r--r--app/graphql/types/group_invitation_type.rb2
-rw-r--r--app/graphql/types/project_invitation_type.rb2
-rw-r--r--lib/api/invitations.rb2
-rw-r--r--lib/api/users.rb8
-rw-r--r--lib/banzai/filter/spaced_link_filter.rb21
-rw-r--r--lib/gitlab/fogbugz_import.rb11
-rw-r--r--lib/gitlab/fogbugz_import/client.rb2
-rw-r--r--lib/gitlab/fogbugz_import/http_adapter.rb21
-rw-r--r--lib/gitlab/string_regex_marker.rb10
-rw-r--r--spec/graphql/types/group_invitation_type_spec.rb2
-rw-r--r--spec/graphql/types/project_invitation_type_spec.rb2
-rw-r--r--spec/lib/banzai/filter/spaced_link_filter_spec.rb10
-rw-r--r--spec/lib/gitlab/fogbugz_import/importer_spec.rb80
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb12
-rw-r--r--spec/requests/api/invitations_spec.rb40
-rw-r--r--spec/requests/api/users_spec.rb171
16 files changed, 254 insertions, 142 deletions
diff --git a/app/graphql/types/group_invitation_type.rb b/app/graphql/types/group_invitation_type.rb
index 06a997bbc14..9410253553c 100644
--- a/app/graphql/types/group_invitation_type.rb
+++ b/app/graphql/types/group_invitation_type.rb
@@ -3,7 +3,7 @@
module Types
class GroupInvitationType < BaseObject
expose_permissions Types::PermissionTypes::Group
- authorize :read_group
+ authorize :admin_group
implements InvitationInterface
diff --git a/app/graphql/types/project_invitation_type.rb b/app/graphql/types/project_invitation_type.rb
index 507dc2d59c3..b76f05e289f 100644
--- a/app/graphql/types/project_invitation_type.rb
+++ b/app/graphql/types/project_invitation_type.rb
@@ -9,7 +9,7 @@ module Types
implements InvitationInterface
- authorize :read_project
+ authorize :admin_project
field :project, Types::ProjectType, null: true,
description: 'Project ID for the project of the invitation.'
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index 1f437ad5bd3..5cade301d81 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -46,6 +46,8 @@ module API
source = find_source(source_type, params[:id])
query = params[:query]
+ authorize_admin_source!(source_type, source)
+
invitations = paginate(retrieve_member_invitations(source, query))
present_member_invitations invitations
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 2608fb87e22..a51dec39e04 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -140,7 +140,10 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
get ":id", feature_category: :users do
+ forbidden!('Not authorized!') unless current_user
+
user = User.find_by(id: params[:id])
+
not_found!('User') unless user && can?(current_user, :read_user, user)
opts = { with: current_user&.admin? ? Entities::UserDetailsWithAdmin : Entities::User, current_user: current_user }
@@ -156,6 +159,7 @@ module API
end
get ":user_id/status", requirements: API::USER_REQUIREMENTS, feature_category: :users do
user = find_user(params[:user_id])
+
not_found!('User') unless user && can?(current_user, :read_user, user)
present user.status || {}, with: Entities::UserStatus
@@ -203,6 +207,8 @@ module API
use :pagination
end
get ':id/following', feature_category: :users do
+ forbidden!('Not authorized!') unless current_user
+
user = find_user(params[:id])
not_found!('User') unless user && can?(current_user, :read_user_profile, user)
@@ -217,6 +223,8 @@ module API
use :pagination
end
get ':id/followers', feature_category: :users do
+ forbidden!('Not authorized!') unless current_user
+
user = find_user(params[:id])
not_found!('User') unless user && can?(current_user, :read_user_profile, user)
diff --git a/lib/banzai/filter/spaced_link_filter.rb b/lib/banzai/filter/spaced_link_filter.rb
index ca26e6d1581..f8d03fd6e50 100644
--- a/lib/banzai/filter/spaced_link_filter.rb
+++ b/lib/banzai/filter/spaced_link_filter.rb
@@ -26,14 +26,17 @@ module Banzai
# Pattern to match a standard markdown link
#
# Rubular: http://rubular.com/r/2EXEQ49rg5
- LINK_OR_IMAGE_PATTERN = %r{
- (?<preview_operator>!)?
- \[(?<text>.+?)\]
- \(
- (?<new_link>.+?)
- (?<title>\ ".+?")?
- \)
- }x.freeze
+ #
+ # This pattern is vulnerable to malicious inputs, so use Gitlab::UntrustedRegexp
+ # to place bounds on execution time
+ LINK_OR_IMAGE_PATTERN = Gitlab::UntrustedRegexp.new(
+ '(?P<preview_operator>!)?' \
+ '\[(?P<text>.+?)\]' \
+ '\(' \
+ '(?P<new_link>.+?)' \
+ '(?P<title>\ ".+?")?' \
+ '\)'
+ )
# Text matching LINK_OR_IMAGE_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
@@ -48,7 +51,7 @@ module Banzai
doc.xpath(TEXT_QUERY).each do |node|
content = node.to_html
- next unless content.match(LINK_OR_IMAGE_PATTERN)
+ next unless LINK_OR_IMAGE_PATTERN.match(content)
html = spaced_link_filter(content)
diff --git a/lib/gitlab/fogbugz_import.rb b/lib/gitlab/fogbugz_import.rb
new file mode 100644
index 00000000000..a4a52edd83e
--- /dev/null
+++ b/lib/gitlab/fogbugz_import.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'fogbugz'
+
+module Gitlab
+ module FogbugzImport
+ # Custom adapter to validate the URL before each request
+ # This way we avoid DNS rebinds or other unsafe requests
+ ::Fogbugz.adapter[:http] = HttpAdapter
+ end
+end
diff --git a/lib/gitlab/fogbugz_import/client.rb b/lib/gitlab/fogbugz_import/client.rb
index dd747a79673..024c1ae0439 100644
--- a/lib/gitlab/fogbugz_import/client.rb
+++ b/lib/gitlab/fogbugz_import/client.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require 'fogbugz'
-
module Gitlab
module FogbugzImport
class Client
diff --git a/lib/gitlab/fogbugz_import/http_adapter.rb b/lib/gitlab/fogbugz_import/http_adapter.rb
new file mode 100644
index 00000000000..bfae7a10f5b
--- /dev/null
+++ b/lib/gitlab/fogbugz_import/http_adapter.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module FogbugzImport
+ class HttpAdapter
+ def initialize(options = {})
+ @root_url = options[:uri]
+ end
+
+ def request(action, options = {})
+ uri = Gitlab::Utils.append_path(@root_url, 'api.asp')
+
+ params = { 'cmd' => action }.merge(options.fetch(:params, {}))
+
+ response = Gitlab::HTTP.post(uri, body: params)
+
+ response.body
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/string_regex_marker.rb b/lib/gitlab/string_regex_marker.rb
index f1982ff914c..8e0167a433e 100644
--- a/lib/gitlab/string_regex_marker.rb
+++ b/lib/gitlab/string_regex_marker.rb
@@ -2,18 +2,20 @@
module Gitlab
class StringRegexMarker < StringRangeMarker
- # rubocop: disable CodeReuse/ActiveRecord
def mark(regex, group: 0, &block)
ranges = []
+ offset = 0
- raw_line.scan(regex) do
- begin_index, end_index = Regexp.last_match.offset(group)
+ while match = regex.match(raw_line[offset..])
+ begin_index = match.begin(group) + offset
+ end_index = match.end(group) + offset
ranges << (begin_index..(end_index - 1))
+
+ offset = end_index
end
super(ranges, &block)
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/spec/graphql/types/group_invitation_type_spec.rb b/spec/graphql/types/group_invitation_type_spec.rb
index dab2d43fc90..9eedc2db81d 100644
--- a/spec/graphql/types/group_invitation_type_spec.rb
+++ b/spec/graphql/types/group_invitation_type_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Types::GroupInvitationType do
specify { expect(described_class.graphql_name).to eq('GroupInvitation') }
- specify { expect(described_class).to require_graphql_authorizations(:read_group) }
+ specify { expect(described_class).to require_graphql_authorizations(:admin_group) }
it 'has the expected fields' do
expected_fields = %w[
diff --git a/spec/graphql/types/project_invitation_type_spec.rb b/spec/graphql/types/project_invitation_type_spec.rb
index 148a763a5fa..5c0b03c2505 100644
--- a/spec/graphql/types/project_invitation_type_spec.rb
+++ b/spec/graphql/types/project_invitation_type_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Types::ProjectInvitationType do
specify { expect(described_class.graphql_name).to eq('ProjectInvitation') }
- specify { expect(described_class).to require_graphql_authorizations(:read_project) }
+ specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
it 'has the expected fields' do
expected_fields = %w[
diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
index 2c64657d69d..820ebeb6945 100644
--- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
@@ -63,6 +63,16 @@ RSpec.describe Banzai::Filter::SpacedLinkFilter do
end
end
+ it 'does not process malicious input' do
+ Timeout.timeout(10) do
+ doc = filter('[ (](' * 60_000)
+
+ found_links = doc.css('a')
+
+ expect(found_links.size).to eq(0)
+ end
+ end
+
it 'converts multiple URLs' do
link1 = '[first](slug one)'
link2 = '[second](http://example.com/slug two)'
diff --git a/spec/lib/gitlab/fogbugz_import/importer_spec.rb b/spec/lib/gitlab/fogbugz_import/importer_spec.rb
index eb0c4da6ce3..9b58b772d1a 100644
--- a/spec/lib/gitlab/fogbugz_import/importer_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/importer_spec.rb
@@ -4,23 +4,11 @@ require 'spec_helper'
RSpec.describe Gitlab::FogbugzImport::Importer do
let(:project) { create(:project_empty_repo) }
- let(:importer) { described_class.new(project) }
- let(:repo) do
- instance_double(Gitlab::FogbugzImport::Repository,
- safe_name: 'vim',
- path: 'vim',
- raw_data: '')
- end
-
- let(:import_data) { { 'repo' => repo } }
- let(:credentials) do
- {
- 'fb_session' => {
- 'uri' => 'https://testing.fogbugz.com',
- 'token' => 'token'
- }
- }
- end
+ let(:fogbugz_project) { { 'ixProject' => project.id, 'sProject' => 'vim' } }
+ let(:import_data) { { 'repo' => fogbugz_project } }
+ let(:base_url) { 'https://testing.fogbugz.com' }
+ let(:token) { 'token' }
+ let(:credentials) { { 'fb_session' => { 'uri' => base_url, 'token' => token } } }
let(:closed_bug) do
{
@@ -46,18 +34,22 @@ RSpec.describe Gitlab::FogbugzImport::Importer do
let(:fogbugz_bugs) { [opened_bug, closed_bug] }
+ subject(:importer) { described_class.new(project) }
+
before do
project.create_import_data(data: import_data, credentials: credentials)
- allow_any_instance_of(::Fogbugz::Interface).to receive(:command).with(:listCategories).and_return([])
- allow_any_instance_of(Gitlab::FogbugzImport::Client).to receive(:cases).and_return(fogbugz_bugs)
+
+ stub_fogbugz('listProjects', projects: { project: [fogbugz_project], count: 1 })
+ stub_fogbugz('listCategories', categories: { category: [], count: 0 })
+ stub_fogbugz('search', cases: { case: fogbugz_bugs, count: fogbugz_bugs.size })
end
it 'imports bugs' do
- expect { importer.execute }.to change { Issue.count }.by(2)
+ expect { subject.execute }.to change { Issue.count }.by(2)
end
it 'imports opened bugs' do
- importer.execute
+ subject.execute
issue = Issue.where(project_id: project.id).find_by_title(opened_bug[:sTitle])
@@ -65,10 +57,54 @@ RSpec.describe Gitlab::FogbugzImport::Importer do
end
it 'imports closed bugs' do
- importer.execute
+ subject.execute
issue = Issue.where(project_id: project.id).find_by_title(closed_bug[:sTitle])
expect(issue.state_id).to eq(Issue.available_states[:closed])
end
+
+ context 'verify url' do
+ context 'when host is localhost' do
+ let(:base_url) { 'https://localhost:3000' }
+
+ it 'does not allow localhost requests' do
+ expect { subject.execute }
+ .to raise_error(
+ ::Gitlab::HTTP::BlockedUrlError,
+ "URL 'https://localhost:3000/api.asp' is blocked: Requests to localhost are not allowed"
+ )
+ end
+ end
+
+ context 'when host is on local network' do
+ let(:base_url) { 'http://192.168.0.1' }
+
+ it 'does not allow localhost requests' do
+ expect { subject.execute }
+ .to raise_error(
+ ::Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://192.168.0.1/api.asp' is blocked: Requests to the local network are not allowed"
+ )
+ end
+ end
+
+ context 'when host is ftp protocol' do
+ let(:base_url) { 'ftp://testing' }
+
+ it 'only accept http and https requests' do
+ expect { subject.execute }
+ .to raise_error(
+ HTTParty::UnsupportedURIScheme,
+ "'ftp://testing/api.asp' Must be HTTP, HTTPS or Generic"
+ )
+ end
+ end
+ end
+
+ def stub_fogbugz(command, response)
+ stub_request(:post, "#{base_url}/api.asp")
+ .with(body: hash_including({ 'cmd' => command, 'token' => token }))
+ .to_return(status: 200, body: response.to_xml(root: :response))
+ end
end
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
index a02be83558c..0cbe44eacf4 100644
--- a/spec/lib/gitlab/string_regex_marker_spec.rb
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -23,9 +23,10 @@ RSpec.describe Gitlab::StringRegexMarker do
context 'with multiple occurrences' do
let(:raw) { %{a <b> <c> d} }
let(:rich) { %{a &lt;b&gt; &lt;c&gt; d}.html_safe }
+ let(:regexp) { /<[a-z]>/ }
subject do
- described_class.new(raw, rich).mark(/<[a-z]>/) do |text, left:, right:, mode:|
+ described_class.new(raw, rich).mark(regexp) do |text, left:, right:, mode:|
%{<strong>#{text}</strong>}.html_safe
end
end
@@ -34,6 +35,15 @@ RSpec.describe Gitlab::StringRegexMarker do
expect(subject).to eq(%{a <strong>&lt;b&gt;</strong> <strong>&lt;c&gt;</strong> d})
expect(subject).to be_html_safe
end
+
+ context 'with a Gitlab::UntrustedRegexp' do
+ let(:regexp) { Gitlab::UntrustedRegexp.new('<[a-z]>') }
+
+ it 'marks the matches' do
+ expect(subject).to eq(%{a <strong>&lt;b&gt;</strong> <strong>&lt;c&gt;</strong> d})
+ expect(subject).to be_html_safe
+ end
+ end
end
end
end
diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb
index 76a4548df8a..b23ba0021e0 100644
--- a/spec/requests/api/invitations_spec.rb
+++ b/spec/requests/api/invitations_spec.rb
@@ -259,22 +259,32 @@ RSpec.describe API::Invitations do
let(:route) { get invitations_url(source, stranger) }
end
- %i[maintainer developer access_requester stranger].each do |type|
+ context "when authenticated as a maintainer" do
+ it 'returns 200' do
+ get invitations_url(source, maintainer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(0)
+ end
+ end
+
+ %i[developer access_requester stranger].each do |type|
context "when authenticated as a #{type}" do
- it 'returns 200' do
+ it 'returns 403' do
user = public_send(type)
get invitations_url(source, user)
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.size).to eq(0)
+ expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
it 'avoids N+1 queries' do
+ invite_member_by_email(source, source_type, email, maintainer)
+
# Establish baseline
get invitations_url(source, maintainer)
@@ -282,7 +292,7 @@ RSpec.describe API::Invitations do
get invitations_url(source, maintainer)
end
- invite_member_by_email(source, source_type, email, maintainer)
+ invite_member_by_email(source, source_type, email2, maintainer)
expect do
get invitations_url(source, maintainer)
@@ -290,7 +300,7 @@ RSpec.describe API::Invitations do
end
it 'does not find confirmed members' do
- get invitations_url(source, developer)
+ get invitations_url(source, maintainer)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -300,10 +310,10 @@ RSpec.describe API::Invitations do
end
it 'finds all members with no query string specified' do
- invite_member_by_email(source, source_type, email, developer)
- invite_member_by_email(source, source_type, email2, developer)
+ invite_member_by_email(source, source_type, email, maintainer)
+ invite_member_by_email(source, source_type, email2, maintainer)
- get invitations_url(source, developer), params: { query: '' }
+ get invitations_url(source, maintainer), params: { query: '' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -314,17 +324,17 @@ RSpec.describe API::Invitations do
end
it 'finds the invitation by invite_email with query string' do
- invite_member_by_email(source, source_type, email, developer)
- invite_member_by_email(source, source_type, email2, developer)
+ invite_member_by_email(source, source_type, email, maintainer)
+ invite_member_by_email(source, source_type, email2, maintainer)
- get invitations_url(source, developer), params: { query: email }
+ get invitations_url(source, maintainer), params: { query: email }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.count).to eq(1)
expect(json_response.first['invite_email']).to eq(email)
- expect(json_response.first['created_by_name']).to eq(developer.name)
+ expect(json_response.first['created_by_name']).to eq(maintainer.name)
expect(json_response.first['user_name']).to eq(nil)
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 383940ce34a..c26f4985e42 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -49,37 +49,6 @@ RSpec.describe API::Users do
end
end
- describe 'GET /users/:id' do
- context 'when unauthenticated' do
- it 'does not contain the note of the user' do
- get api("/users/#{user.id}")
-
- expect(json_response).not_to have_key('note')
- end
- end
-
- context 'when authenticated' do
- context 'as an admin' do
- it 'contains the note of the user' do
- get api("/users/#{user.id}", admin)
-
- expect(json_response).to have_key('note')
- expect(json_response['note']).to eq(user.note)
- expect(json_response).to have_key('sign_in_count')
- end
- end
-
- context 'as a regular user' do
- it 'does not contain the note of the user' do
- get api("/users/#{user.id}", user)
-
- expect(json_response).not_to have_key('note')
- expect(json_response).not_to have_key('sign_in_count')
- end
- end
- end
- end
-
describe "PUT /users/:id" do
context 'when user is an admin' do
it "updates note of the user" do
@@ -523,6 +492,8 @@ RSpec.describe API::Users do
end
describe "GET /users/:id" do
+ let_it_be(:user2, reload: true) { create(:user, username: 'another_user') }
+
it "returns a user by id" do
get api("/users/#{user.id}", user)
@@ -560,6 +531,64 @@ RSpec.describe API::Users do
expect(json_response.keys).not_to include 'trial'
end
+ it 'returns a 404 if the target user is present but inaccessible' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_user, user2).and_return(false)
+
+ get api("/users/#{user2.id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns the `created_at` field for public users' do
+ get api("/users/#{user2.id}", user)
+
+ expect(response).to match_response_schema('public_api/v4/user/basic')
+ expect(json_response.keys).to include('created_at')
+ end
+
+ it 'does not return the `created_at` field for private users' do
+ get api("/users/#{private_user.id}", user)
+
+ expect(response).to match_response_schema('public_api/v4/user/basic')
+ expect(json_response.keys).not_to include('created_at')
+ end
+
+ it 'returns the `followers` field for public users' do
+ get api("/users/#{user2.id}", user)
+
+ expect(response).to match_response_schema('public_api/v4/user/basic')
+ expect(json_response.keys).to include('followers')
+ end
+
+ it 'does not return the `followers` field for private users' do
+ get api("/users/#{private_user.id}", user)
+
+ expect(response).to match_response_schema('public_api/v4/user/basic')
+ expect(json_response.keys).not_to include('followers')
+ end
+
+ it 'returns the `following` field for public users' do
+ get api("/users/#{user2.id}", user)
+
+ expect(response).to match_response_schema('public_api/v4/user/basic')
+ expect(json_response.keys).to include('following')
+ end
+
+ it 'does not return the `following` field for private users' do
+ get api("/users/#{private_user.id}", user)
+
+ expect(response).to match_response_schema('public_api/v4/user/basic')
+ expect(json_response.keys).not_to include('following')
+ end
+
+ it 'does not contain the note of the user' do
+ get api("/users/#{user.id}", user)
+
+ expect(json_response).not_to have_key('note')
+ expect(json_response).not_to have_key('sign_in_count')
+ end
+
context 'when job title is present' do
let(:job_title) { 'Fullstack Engineer' }
@@ -576,6 +605,14 @@ RSpec.describe API::Users do
end
context 'when authenticated as admin' do
+ it 'contains the note of the user' do
+ get api("/users/#{user.id}", admin)
+
+ expect(json_response).to have_key('note')
+ expect(json_response['note']).to eq(user.note)
+ expect(json_response).to have_key('sign_in_count')
+ end
+
it 'includes the `is_admin` field' do
get api("/users/#{user.id}", admin)
@@ -636,62 +673,10 @@ RSpec.describe API::Users do
end
context 'for an anonymous user' do
- it "returns a user by id" do
- get api("/users/#{user.id}")
-
- expect(response).to match_response_schema('public_api/v4/user/basic')
- expect(json_response['username']).to eq(user.username)
- end
-
- it "returns a 404 if the target user is present but inaccessible" do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false)
-
- get api("/users/#{user.id}")
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
-
- it "returns the `created_at` field for public users" do
- get api("/users/#{user.id}")
-
- expect(response).to match_response_schema('public_api/v4/user/basic')
- expect(json_response.keys).to include 'created_at'
- end
-
- it "does not return the `created_at` field for private users" do
- get api("/users/#{private_user.id}")
-
- expect(response).to match_response_schema('public_api/v4/user/basic')
- expect(json_response.keys).not_to include 'created_at'
- end
-
- it "returns the `followers` field for public users" do
- get api("/users/#{user.id}")
-
- expect(response).to match_response_schema('public_api/v4/user/basic')
- expect(json_response.keys).to include 'followers'
- end
-
- it "does not return the `followers` field for private users" do
- get api("/users/#{private_user.id}")
-
- expect(response).to match_response_schema('public_api/v4/user/basic')
- expect(json_response.keys).not_to include 'followers'
- end
-
- it "returns the `following` field for public users" do
+ it 'returns 403' do
get api("/users/#{user.id}")
- expect(response).to match_response_schema('public_api/v4/user/basic')
- expect(json_response.keys).to include 'following'
- end
-
- it "does not return the `following` field for private users" do
- get api("/users/#{private_user.id}")
-
- expect(response).to match_response_schema('public_api/v4/user/basic')
- expect(json_response.keys).not_to include 'following'
+ expect(response).to have_gitlab_http_status(:forbidden)
end
end
@@ -784,6 +769,14 @@ RSpec.describe API::Users do
describe 'GET /users/:id/followers' do
let(:follower) { create(:user) }
+ context 'for an anonymous user' do
+ it 'returns 403' do
+ get api("/users/#{user.id}")
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
context 'user has followers' do
it 'lists followers' do
follower.follow(user)
@@ -819,6 +812,14 @@ RSpec.describe API::Users do
describe 'GET /users/:id/following' do
let(:followee) { create(:user) }
+ context 'for an anonymous user' do
+ it 'returns 403' do
+ get api("/users/#{user.id}")
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
context 'user has followers' do
it 'lists following user' do
user.follow(followee)