Commit 689c6bc8 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-starred-projects-api-fix' into 'master'

Do not expose starred projects of users with private profile via API

See merge request gitlab-org/security/gitlab!1075
parents 59584a7e 0a27fcc3
# frozen_string_literal: true
class StarredProjectsFinder < ProjectsFinder
include Gitlab::Allowable
def initialize(user, params: {}, current_user: nil)
@user = user
super(
params: params,
current_user: current_user,
project_ids_relation: user.starred_projects.select(:id)
)
end
def execute
# Do not show starred projects if the user has a private profile.
return Project.none unless can?(current_user, :read_user_profile, @user)
super
end
end
---
title: Do not expose starred projects of users with private profile via API
merge_request:
author:
type: security
......@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe StarredProjectsFinder do
let(:project1) { create(:project, :public, :empty_repo) }
let(:project2) { create(:project, :public, :empty_repo) }
let(:other_project) { create(:project, :public, :empty_repo) }
let(:private_project) { create(:project, :private, :empty_repo) }
let(:user) { create(:user) }
let(:other_user) { create(:user) }
......@@ -13,6 +13,9 @@ RSpec.describe StarredProjectsFinder do
before do
user.toggle_star(project1)
user.toggle_star(project2)
private_project.add_maintainer(user)
user.toggle_star(private_project)
end
describe '#execute' do
......@@ -20,22 +23,56 @@ RSpec.describe StarredProjectsFinder do
subject { finder.execute }
describe 'as same user' do
let(:current_user) { user }
context 'user has a public profile' do
describe 'as same user' do
let(:current_user) { user }
it { is_expected.to contain_exactly(project1, project2) }
end
it { is_expected.to contain_exactly(project1, project2, private_project) }
end
describe 'as other user' do
let(:current_user) { other_user }
describe 'as other user' do
let(:current_user) { other_user }
it { is_expected.to contain_exactly(project1, project2) }
end
it { is_expected.to contain_exactly(project1, project2) }
describe 'as no user' do
let(:current_user) { nil }
it { is_expected.to contain_exactly(project1, project2) }
end
end
describe 'as no user' do
let(:current_user) { nil }
context 'user has a private profile' do
before do
user.update!(private_profile: true)
end
describe 'as same user' do
let(:current_user) { user }
it { is_expected.to contain_exactly(project1, project2, private_project) }
end
describe 'as other user' do
context 'user does not have access to view the private profile' do
let(:current_user) { other_user }
it { is_expected.to be_empty }
end
context 'user has access to view the private profile', :enable_admin_mode do
let(:current_user) { create(:admin) }
it { is_expected.to contain_exactly(project1, project2, private_project) }
end
end
describe 'as no user' do
let(:current_user) { nil }
it { is_expected.to contain_exactly(project1, project2) }
it { is_expected.to be_empty }
end
end
end
end
......@@ -70,4 +70,31 @@ RSpec.describe 'Getting starredProjects of the user' do
)
end
end
context 'the user has a private profile' do
before do
user.update!(private_profile: true)
post_graphql(query, current_user: current_user)
end
context 'the current user does not have access to view the private profile of the user' do
let(:current_user) { create(:user) }
it 'finds no projects' do
expect(starred_projects).to be_empty
end
end
context 'the current user has access to view the private profile of the user' do
let(:current_user) { create(:admin) }
it 'finds all projects starred by the user, which the current user has access to' do
expect(starred_projects).to contain_exactly(
a_hash_including('id' => global_id_of(project_a)),
a_hash_including('id' => global_id_of(project_b)),
a_hash_including('id' => global_id_of(project_c))
)
end
end
end
end
......@@ -1255,13 +1255,46 @@ RSpec.describe API::Projects do
expect(json_response['message']).to eq('404 User Not Found')
end
it 'returns projects filtered by user' do
get api("/users/#{user3.id}/starred_projects/", user)
context 'with a public profile' do
it 'returns projects filtered by user' do
get api("/users/#{user3.id}/starred_projects/", 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.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project3.id)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |project| project['id'] })
.to contain_exactly(project.id, project2.id, project3.id)
end
end
context 'with a private profile' do
before do
user3.update!(private_profile: true)
user3.reload
end
context 'user does not have access to view the private profile' do
it 'returns no projects' do
get api("/users/#{user3.id}/starred_projects/", 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).to be_empty
end
end
context 'user has access to view the private profile' do
it 'returns projects filtered by user' do
get api("/users/#{user3.id}/starred_projects/", admin)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |project| project['id'] })
.to contain_exactly(project.id, project2.id, project3.id)
end
end
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment