Commit 5bb80a58 authored by Rémy Coutable's avatar Rémy Coutable

Ensure the Insights configuration project is part of the group and is...

Ensure the Insights configuration project is part of the group and is accessible to the current user
Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent 66de6464
...@@ -44,7 +44,7 @@ module InsightsActions ...@@ -44,7 +44,7 @@ module InsightsActions
end end
def query_param def query_param
@query_param ||= params[:query] @query_param ||= params[:query] || {}
end end
def period_param def period_param
......
...@@ -4,6 +4,7 @@ class Groups::InsightsController < Groups::ApplicationController ...@@ -4,6 +4,7 @@ class Groups::InsightsController < Groups::ApplicationController
include InsightsActions include InsightsActions
before_action :authorize_read_group! before_action :authorize_read_group!
before_action :authorize_read_insights_config_project!
private private
...@@ -11,6 +12,12 @@ class Groups::InsightsController < Groups::ApplicationController ...@@ -11,6 +12,12 @@ class Groups::InsightsController < Groups::ApplicationController
render_404 unless can?(current_user, :read_group, group) render_404 unless can?(current_user, :read_group, group)
end end
def authorize_read_insights_config_project!
insights_config_project = group.insights_config_project
render_404 if insights_config_project && !can?(current_user, :read_project, insights_config_project)
end
def insights_entity def insights_entity
group group
end end
......
...@@ -10,32 +10,32 @@ module InsightsFeature ...@@ -10,32 +10,32 @@ module InsightsFeature
feature_available?(:insights) feature_available?(:insights)
end end
def insights_config(follow_group: true) def insights_config_project
strong_memoize(:insights_config_project) do
case self case self
when Group when Group
# When there's a config file, we use it regardless it's valid or not insight&.project
if insight&.project&.project_insights_config_yaml
insight.project.insights_config(follow_group: false)
else # When there's nothing, then we use the default
default_insights_config
end
when Project when Project
yaml = project_insights_config_yaml if insights_config_yaml
self
else
group&.insights_config_project
end
end
end
end
# When there's a config file, we use it regardless it's valid or not def insights_config
if yaml
strong_memoize(:insights_config) do strong_memoize(:insights_config) do
yaml = insights_config_project&.insights_config_yaml
if yaml
::Gitlab::Config::Loader::Yaml.new(yaml).load! ::Gitlab::Config::Loader::Yaml.new(yaml).load!
rescue Gitlab::Config::Loader::FormatError
nil
end
# When we're following the group and there's a group then we use it
elsif follow_group && group
group.insights_config
# A project might not have a group, then we just use the default
else else
default_insights_config default_insights_config
end end
rescue Gitlab::Config::Loader::FormatError
nil
end end
end end
...@@ -50,8 +50,10 @@ module InsightsFeature ...@@ -50,8 +50,10 @@ module InsightsFeature
protected protected
def project_insights_config_yaml def insights_config_yaml
strong_memoize(:project_insights_config_yaml) do raise NotImplementedError unless is_a?(Project)
strong_memoize(:insights_config_yaml) do
next if repository.empty? next if repository.empty?
repository.insights_config_for(repository.root_ref) repository.insights_config_for(repository.root_ref)
......
...@@ -35,6 +35,12 @@ module EE ...@@ -35,6 +35,12 @@ module EE
params.delete(:extra_shared_runners_minutes_limit) params.delete(:extra_shared_runners_minutes_limit)
end end
insight_project_id = params.dig(:insight_attributes, :project_id)
if insight_project_id
group_projects = GroupProjectsFinder.new(group: group, current_user: current_user, options: { only_owned: true, include_subgroups: true }).execute
params.delete(:insight_attributes) unless group_projects.exists?(insight_project_id) # rubocop:disable CodeReuse/ActiveRecord
end
super super
end end
......
---
title: Ensure the Insights configuration project is part of the group and is accessible to the current user
merge_request:
author:
type: security
...@@ -54,7 +54,7 @@ module Gitlab ...@@ -54,7 +54,7 @@ module Gitlab
attr_reader :entity, :current_user, :opts attr_reader :entity, :current_user, :opts
def finder def finder
issuable_type = opts[:issuable_type].to_sym issuable_type = opts[:issuable_type]&.to_sym
FINDERS[issuable_type] || FINDERS[issuable_type] ||
raise(InvalidIssuableTypeError, "Invalid `:issuable_type` option: `#{opts[:issuable_type]}`. Allowed values are #{FINDERS.keys}!") raise(InvalidIssuableTypeError, "Invalid `:issuable_type` option: `#{opts[:issuable_type]}`. Allowed values are #{FINDERS.keys}!")
......
require 'spec_helper'
describe Groups::InsightsController do
set(:parent_group) { create(:group, :private) }
set(:nested_group) { create(:group, :private, parent: parent_group) }
set(:project) { create(:project, :private) }
set(:insight) { create(:insight, group: parent_group, project: project) }
set(:user) { create(:user) }
let(:query_params) { { chart_type: 'bar', query: { issuable_type: 'issue', collection_labels: ['bug'] } } }
before do
stub_licensed_features(insights: true)
sign_in(user)
parent_group.add_developer(user)
nested_group.add_developer(user)
end
shared_examples '404 status' do
it 'returns 404 status' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
shared_examples '200 status' do
it 'returns 200 status' do
subject
expect(response).to have_gitlab_http_status(200)
end
end
context 'when insights configuration project cannot be read by current user' do
context 'when visiting the parent group' do
describe 'GET #show.html' do
subject { get :show, params: { group_id: parent_group.to_param } }
it_behaves_like '404 status'
end
describe 'GET #show.json' do
subject { get :show, params: { group_id: parent_group.to_param }, format: :json }
it_behaves_like '404 status'
end
describe 'POST #query' do
subject { post :query, params: query_params.merge(group_id: parent_group.to_param) }
it_behaves_like '404 status'
end
end
context 'when visiting a nested group' do
describe 'GET #show.html' do
subject { get :show, params: { group_id: nested_group.to_param } }
# The following expectation should be changed to
# it_behaves_like '404 status'
# once https://gitlab.com/gitlab-org/gitlab-ee/issues/11340 is implemented.
it_behaves_like '200 status'
end
describe 'GET #show.json' do
subject { get :show, params: { group_id: nested_group.to_param }, format: :json }
# The following expectation should be changed to
# it_behaves_like '404 status'
# once https://gitlab.com/gitlab-org/gitlab-ee/issues/11340 is implemented.
it_behaves_like '200 status'
# The following expectation should be removed
# once https://gitlab.com/gitlab-org/gitlab-ee/issues/11340 is implemented.
it 'does return the default config' do
subject
expect(response.parsed_body).to eq(parent_group.default_insights_config.to_json)
end
end
describe 'POST #query.json' do
subject { post :query, params: query_params.merge(group_id: nested_group.to_param), format: :json }
# The following expectation should be changed to
# it_behaves_like '404 status'
# once https://gitlab.com/gitlab-org/gitlab-ee/issues/11340 is implemented.
it_behaves_like '200 status'
end
end
end
context 'when insights configuration project can be read by current user' do
before do
project.add_reporter(user)
end
context 'when the configuration is attached to the current group' do
describe 'GET #show.html' do
subject { get :show, params: { group_id: parent_group.to_param } }
it_behaves_like '200 status'
end
describe 'GET #show.sjon' do
subject { get :show, params: { group_id: parent_group.to_param }, format: :json }
it_behaves_like '200 status'
end
describe 'POST #query.json' do
subject { post :query, params: query_params.merge(group_id: parent_group.to_param), format: :json }
it_behaves_like '200 status'
end
end
context 'when the configuration is attached to a nested group' do
describe 'GET #show.html' do
subject { get :show, params: { group_id: nested_group.to_param } }
it_behaves_like '200 status'
end
describe 'GET #show.json' do
subject { get :show, params: { group_id: nested_group.to_param }, format: :json }
it_behaves_like '200 status'
end
describe 'POST #query.json' do
subject { post :query, params: query_params.merge(group_id: nested_group.to_param), format: :json }
it_behaves_like '200 status'
end
end
end
end
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :insight do
group
project
end
factory :insights_issues_by_team, class: Hash do factory :insights_issues_by_team, class: Hash do
initialize_with do initialize_with do
{ {
......
...@@ -425,10 +425,26 @@ describe Group do ...@@ -425,10 +425,26 @@ describe Group do
context 'with an invalid config file' do context 'with an invalid config file' do
let(:insights_file_content) { ': foo bar' } let(:insights_file_content) { ': foo bar' }
it 'returns the insights config data' do it 'returns nil' do
expect(group.insights_config).to be_nil expect(group.insights_config).to be_nil
end end
end end
end end
context 'when group has an Insights project configured which is in a nested group' do
before do
nested_group = create(:group, parent: group)
project = create(:project, :custom_repo, group: nested_group, files: { ::Gitlab::Insights::CONFIG_FILE_PATH => insights_file_content })
group.create_insight!(project: project)
end
let(:insights_file_content) { 'key: monthlyBugsCreated' }
it 'returns the insights config data' do
insights_config = group.insights_config
expect(insights_config).to eq(key: 'monthlyBugsCreated')
end
end
end end
end end
...@@ -1747,7 +1747,7 @@ describe Project do ...@@ -1747,7 +1747,7 @@ describe Project do
context 'when the group has an Insights config from another project' do context 'when the group has an Insights config from another project' do
let(:config_project) do let(:config_project) do
create(:project, :custom_repo, files: { ::Gitlab::Insights::CONFIG_FILE_PATH => insights_file_content }) create(:project, :custom_repo, group: group, files: { ::Gitlab::Insights::CONFIG_FILE_PATH => insights_file_content })
end end
before do before do
...@@ -1761,6 +1761,18 @@ describe Project do ...@@ -1761,6 +1761,18 @@ describe Project do
expect(project.insights_config).to eq(config_project.insights_config) expect(project.insights_config).to eq(config_project.insights_config)
expect(project.insights_config).to eq(group.insights_config) expect(project.insights_config).to eq(group.insights_config)
end end
context 'when the project is inside a nested group' do
let(:nested_group) { create(:group, parent: group) }
let(:project) { create(:project, group: nested_group) }
# The following expectaction should be changed to
# expect(project.insights_config).to eq(config_project.insights_config)
# once https://gitlab.com/gitlab-org/gitlab-ee/issues/11340 is implemented.
it 'returns the project default config' do
expect(project.insights_config).to eq(project.default_insights_config)
end
end
end end
context 'with an invalid config file' do context 'with an invalid config file' do
...@@ -1783,25 +1795,22 @@ describe Project do ...@@ -1783,25 +1795,22 @@ describe Project do
let(:insights_file_content) { 'key: monthlyBugsCreated' } let(:insights_file_content) { 'key: monthlyBugsCreated' }
it 'returns the insights config data' do it 'returns the insights config data' do
insights_config = project.insights_config expect(project.insights_config).to eq(key: 'monthlyBugsCreated')
expect(insights_config).to eq(key: 'monthlyBugsCreated')
end end
context 'when the project is inside a group having another config' do context 'when the project is inside a group having another config' do
let(:group) { create(:group) }
let(:config_project) do let(:config_project) do
create(:project, :custom_repo, files: { ::Gitlab::Insights::CONFIG_FILE_PATH => ': foo bar' }) create(:project, :custom_repo, group: group, files: { ::Gitlab::Insights::CONFIG_FILE_PATH => ': foo bar' })
end end
before do before do
project.group = create(:group) project.group = group
project.group.create_insight!(project: config_project) project.group.create_insight!(project: config_project)
end end
it 'returns the project insights config data' do it 'returns the project insights config data' do
insights_config = project.insights_config expect(project.insights_config).to eq(key: 'monthlyBugsCreated')
expect(insights_config).to eq(key: 'monthlyBugsCreated')
end end
end end
end end
...@@ -1814,12 +1823,13 @@ describe Project do ...@@ -1814,12 +1823,13 @@ describe Project do
end end
context 'when the project is inside a group having another config' do context 'when the project is inside a group having another config' do
let(:group) { create(:group) }
let(:config_project) do let(:config_project) do
create(:project, :custom_repo, files: { ::Gitlab::Insights::CONFIG_FILE_PATH => 'key: monthlyBugsCreated' }) create(:project, :custom_repo, group: group, files: { ::Gitlab::Insights::CONFIG_FILE_PATH => 'key: monthlyBugsCreated' })
end end
before do before do
project.group = create(:group) project.group = group
project.group.create_insight!(project: config_project) project.group.create_insight!(project: config_project)
end end
......
...@@ -204,6 +204,58 @@ describe Groups::UpdateService, '#execute' do ...@@ -204,6 +204,58 @@ describe Groups::UpdateService, '#execute' do
end end
end end
context 'updating insight_attributes.project_id param' do
let(:attrs) { { insight_attributes: { project_id: private_project.id } } }
shared_examples 'successful update of the Insights project' do
it 'updates the Insights project' do
update_group(group, user, attrs)
expect(group.insight.project).to eq(private_project)
end
end
shared_examples 'ignorance of the Insights project ID' do
it 'ignores the Insights project ID' do
update_group(group, user, attrs)
expect(group.insight).to be_nil
end
end
context 'when project is not in the group' do
let(:private_project) { create(:project, :private) }
context 'when user can read the project' do
before do
private_project.add_maintainer(user)
end
it_behaves_like 'ignorance of the Insights project ID'
end
context 'when user cannot read the project' do
it_behaves_like 'ignorance of the Insights project ID'
end
end
context 'when project is in the group' do
let(:private_project) { create(:project, :private, group: group) }
context 'when user can read the project' do
before do
private_project.add_maintainer(user)
end
it_behaves_like 'successful update of the Insights project'
end
context 'when user cannot read the project' do
it_behaves_like 'ignorance of the Insights project ID'
end
end
end
def update_group(group, user, opts) def update_group(group, user, opts)
Groups::UpdateService.new(group, user, opts).execute Groups::UpdateService.new(group, user, opts).execute
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