Commit 7ac6a621 authored by Nathan Friend's avatar Nathan Friend

Hide project-level CI/CD Analytics for Guests

This commit updates the project-level CI/CD Analytics page to not be
accessible by Guest users of private projects.

Changelog: security
parent 9d79388a
...@@ -9,7 +9,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -9,7 +9,7 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :set_pipeline_path, only: [:show] before_action :set_pipeline_path, only: [:show]
before_action :authorize_read_pipeline! before_action :authorize_read_pipeline!
before_action :authorize_read_build!, only: [:index, :show] before_action :authorize_read_build!, only: [:index, :show]
before_action :authorize_read_analytics!, only: [:charts] before_action :authorize_read_ci_cd_analytics!, only: [:charts]
before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables] before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables]
before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action do before_action do
......
...@@ -2,8 +2,12 @@ ...@@ -2,8 +2,12 @@
module Resolvers module Resolvers
class ProjectPipelineStatisticsResolver < BaseResolver class ProjectPipelineStatisticsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
type Types::Ci::AnalyticsType, null: true type Types::Ci::AnalyticsType, null: true
authorizes_object!
authorize :read_ci_cd_analytics
def resolve def resolve
weekly_stats = Gitlab::Ci::Charts::WeekChart.new(object) weekly_stats = Gitlab::Ci::Charts::WeekChart.new(object)
monthly_stats = Gitlab::Ci::Charts::MonthChart.new(object) monthly_stats = Gitlab::Ci::Charts::MonthChart.new(object)
......
...@@ -284,6 +284,7 @@ class ProjectPolicy < BasePolicy ...@@ -284,6 +284,7 @@ class ProjectPolicy < BasePolicy
enable :read_confidential_issues enable :read_confidential_issues
enable :read_package enable :read_package
enable :read_product_analytics enable :read_product_analytics
enable :read_ci_cd_analytics
end end
# We define `:public_user_access` separately because there are cases in gitlab-ee # We define `:public_user_access` separately because there are cases in gitlab-ee
...@@ -484,6 +485,7 @@ class ProjectPolicy < BasePolicy ...@@ -484,6 +485,7 @@ class ProjectPolicy < BasePolicy
prevent(:read_insights) prevent(:read_insights)
prevent(:read_cycle_analytics) prevent(:read_cycle_analytics)
prevent(:read_repository_graphs) prevent(:read_repository_graphs)
prevent(:read_ci_cd_analytics)
end end
rule { wiki_disabled }.policy do rule { wiki_disabled }.policy do
...@@ -559,6 +561,7 @@ class ProjectPolicy < BasePolicy ...@@ -559,6 +561,7 @@ class ProjectPolicy < BasePolicy
enable :read_cycle_analytics enable :read_cycle_analytics
enable :read_pages_content enable :read_pages_content
enable :read_analytics enable :read_analytics
enable :read_ci_cd_analytics
enable :read_insights enable :read_insights
# NOTE: may be overridden by IssuePolicy # NOTE: may be overridden by IssuePolicy
......
...@@ -46,6 +46,7 @@ module Sidebars ...@@ -46,6 +46,7 @@ module Sidebars
def ci_cd_analytics_menu_item def ci_cd_analytics_menu_item
if !context.project.feature_available?(:builds, context.current_user) || if !context.project.feature_available?(:builds, context.current_user) ||
!can?(context.current_user, :read_build, context.project) || !can?(context.current_user, :read_build, context.project) ||
!can?(context.current_user, :read_ci_cd_analytics, context.project) ||
context.project.empty_repo? context.project.empty_repo?
return ::Sidebars::NilMenuItem.new(item_id: :ci_cd_analytics) return ::Sidebars::NilMenuItem.new(item_id: :ci_cd_analytics)
end end
......
...@@ -5,14 +5,24 @@ require 'spec_helper' ...@@ -5,14 +5,24 @@ require 'spec_helper'
RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project, :private) }
let_it_be(:guest) { create(:user) }
let_it_be(:reporter) { create(:user) }
let(:current_user) { reporter }
before_all do
project.add_guest(guest)
project.add_reporter(reporter)
end
specify do specify do
expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType) expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
end end
def resolve_statistics(project, args) def resolve_statistics(project, args)
resolve(described_class, obj: project, args: args) ctx = { current_user: current_user }
resolve(described_class, obj: project, args: args, ctx: ctx)
end end
describe '#resolve' do describe '#resolve' do
...@@ -32,5 +42,15 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do ...@@ -32,5 +42,15 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
:pipeline_times_values :pipeline_times_values
) )
end end
context 'when the user does not have access to the CI/CD analytics data' do
let(:current_user) { guest }
it 'returns nil' do
result = resolve_statistics(project, {})
expect(result).to be_nil
end
end
end end
end end
...@@ -4,15 +4,19 @@ require 'spec_helper' ...@@ -4,15 +4,19 @@ require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:guest) do
create(:user).tap { |u| project.add_guest(u) }
end
let(:user) { project.owner } let(:owner) { project.owner }
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, current_ref: project.repository.root_ref) } let(:current_user) { owner }
let(:context) { Sidebars::Projects::Context.new(current_user: current_user, container: project, current_ref: project.repository.root_ref) }
subject { described_class.new(context) } subject { described_class.new(context) }
describe '#render?' do describe '#render?' do
context 'whe user cannot read analytics' do context 'whe user cannot read analytics' do
let(:user) { nil } let(:current_user) { nil }
it 'returns false' do it 'returns false' do
expect(subject.render?).to be false expect(subject.render?).to be false
...@@ -79,7 +83,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do ...@@ -79,7 +83,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
end end
describe 'when the user does not have access' do describe 'when the user does not have access' do
let(:user) { nil } let(:current_user) { guest }
specify { is_expected.to be_nil } specify { is_expected.to be_nil }
end end
...@@ -99,7 +103,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do ...@@ -99,7 +103,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
end end
describe 'when the user does not have access' do describe 'when the user does not have access' do
let(:user) { nil } let(:current_user) { nil }
specify { is_expected.to be_nil } specify { is_expected.to be_nil }
end end
...@@ -111,7 +115,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do ...@@ -111,7 +115,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
specify { is_expected.not_to be_nil } specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do describe 'when the user does not have access' do
let(:user) { nil } let(:current_user) { nil }
specify { is_expected.to be_nil } specify { is_expected.to be_nil }
end end
......
...@@ -1131,12 +1131,20 @@ RSpec.describe ProjectPolicy do ...@@ -1131,12 +1131,20 @@ RSpec.describe ProjectPolicy do
let_it_be(:project_with_analytics_enabled) { create(:project, :analytics_enabled) } let_it_be(:project_with_analytics_enabled) { create(:project, :analytics_enabled) }
before do before do
project_with_analytics_disabled.add_guest(guest)
project_with_analytics_private.add_guest(guest)
project_with_analytics_enabled.add_guest(guest)
project_with_analytics_disabled.add_reporter(reporter)
project_with_analytics_private.add_reporter(reporter)
project_with_analytics_enabled.add_reporter(reporter)
project_with_analytics_disabled.add_developer(developer) project_with_analytics_disabled.add_developer(developer)
project_with_analytics_private.add_developer(developer) project_with_analytics_private.add_developer(developer)
project_with_analytics_enabled.add_developer(developer) project_with_analytics_enabled.add_developer(developer)
end end
context 'when analytics is enabled for the project' do context 'when analytics is disabled for the project' do
let(:project) { project_with_analytics_disabled } let(:project) { project_with_analytics_disabled }
context 'for guest user' do context 'for guest user' do
...@@ -1145,6 +1153,16 @@ RSpec.describe ProjectPolicy do ...@@ -1145,6 +1153,16 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_disallowed(:read_cycle_analytics) } it { is_expected.to be_disallowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) } it { is_expected.to be_disallowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) } it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end
context 'for reporter user' do
let(:current_user) { reporter }
it { is_expected.to be_disallowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end end
context 'for developer' do context 'for developer' do
...@@ -1153,6 +1171,7 @@ RSpec.describe ProjectPolicy do ...@@ -1153,6 +1171,7 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_disallowed(:read_cycle_analytics) } it { is_expected.to be_disallowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) } it { is_expected.to be_disallowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) } it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end end
end end
...@@ -1162,9 +1181,19 @@ RSpec.describe ProjectPolicy do ...@@ -1162,9 +1181,19 @@ RSpec.describe ProjectPolicy do
context 'for guest user' do context 'for guest user' do
let(:current_user) { guest } let(:current_user) { guest }
it { is_expected.to be_disallowed(:read_cycle_analytics) } it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) } it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) } it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end
context 'for reporter user' do
let(:current_user) { reporter }
it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end end
context 'for developer' do context 'for developer' do
...@@ -1173,18 +1202,29 @@ RSpec.describe ProjectPolicy do ...@@ -1173,18 +1202,29 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_allowed(:read_cycle_analytics) } it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) } it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) } it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end end
end end
context 'when analytics is enabled for the project' do context 'when analytics is enabled for the project' do
let(:project) { project_with_analytics_private } let(:project) { project_with_analytics_enabled }
context 'for guest user' do context 'for guest user' do
let(:current_user) { guest } let(:current_user) { guest }
it { is_expected.to be_disallowed(:read_cycle_analytics) } it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) } it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) } it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end
context 'for reporter user' do
let(:current_user) { reporter }
it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end end
context 'for developer' do context 'for developer' do
...@@ -1193,6 +1233,7 @@ RSpec.describe ProjectPolicy do ...@@ -1193,6 +1233,7 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_allowed(:read_cycle_analytics) } it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) } it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) } it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end end
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