Commit 62f792e3 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'add_support_for_group_and_user_id' into 'master'

Add support for user_id, group_id and group_path

See merge request gitlab-org/gitlab!76595
parents c075a9b7 a2187ab0
...@@ -123,6 +123,8 @@ class Group < Namespace ...@@ -123,6 +123,8 @@ class Group < Namespace
scope :by_id, ->(groups) { where(id: groups) } scope :by_id, ->(groups) { where(id: groups) }
scope :by_ids_or_paths, -> (ids, paths) { by_id(ids).or(where(path: paths)) }
scope :for_authorized_group_members, -> (user_ids) do scope :for_authorized_group_members, -> (user_ids) do
joins(:group_members) joins(:group_members)
.where(members: { user_id: user_ids }) .where(members: { user_id: user_ids })
...@@ -214,6 +216,10 @@ class Group < Namespace ...@@ -214,6 +216,10 @@ class Group < Namespace
Set.new(group_ids) Set.new(group_ids)
end end
def get_ids_by_ids_or_paths(ids, paths)
by_ids_or_paths(ids, paths).pluck(:id)
end
private private
def public_to_user_arel(user) def public_to_user_arel(user)
......
...@@ -466,7 +466,7 @@ class User < ApplicationRecord ...@@ -466,7 +466,7 @@ class User < ApplicationRecord
scope :dormant, -> { with_state(:active).human_or_service_user.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) } scope :dormant, -> { with_state(:active).human_or_service_user.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) }
scope :with_no_activity, -> { with_state(:active).human_or_service_user.where(last_activity_on: nil) } scope :with_no_activity, -> { with_state(:active).human_or_service_user.where(last_activity_on: nil) }
scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) } scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) }
scope :get_ids_by_username, -> (username) { where(username: username).pluck(:id) } scope :by_ids_or_usernames, -> (ids, usernames) { where(username: usernames).or(where(id: ids)) }
strip_attributes! :name strip_attributes! :name
...@@ -836,6 +836,10 @@ class User < ApplicationRecord ...@@ -836,6 +836,10 @@ class User < ApplicationRecord
def single_user def single_user
User.non_internal.first if single_user? User.non_internal.first if single_user?
end end
def get_ids_by_ids_or_usernames(ids, usernames)
by_ids_or_usernames(ids, usernames).pluck(:id)
end
end end
# #
......
...@@ -42,11 +42,12 @@ module Security ...@@ -42,11 +42,12 @@ module Security
scanners: rule[:scanners], scanners: rule[:scanners],
rule_type: :report_approver, rule_type: :report_approver,
severity_levels: rule[:severity_levels], severity_levels: rule[:severity_levels],
user_ids: project.users.get_ids_by_username(action_info[:approvers]), user_ids: users_ids(action_info[:user_approvers_ids], action_info[:user_approvers]),
vulnerabilities_allowed: rule[:vulnerabilities_allowed], vulnerabilities_allowed: rule[:vulnerabilities_allowed],
report_type: :scan_finding, report_type: :scan_finding,
orchestration_policy_idx: policy_index, orchestration_policy_idx: policy_index,
vulnerability_states: rule[:vulnerability_states] vulnerability_states: rule[:vulnerability_states],
group_ids: groups_ids(action_info[:group_approvers_ids], action_info[:group_approvers])
} }
end end
...@@ -56,6 +57,14 @@ module Security ...@@ -56,6 +57,14 @@ module Security
"#{truncated} #{rule_index + 1}" "#{truncated} #{rule_index + 1}"
end end
def users_ids(user_ids, user_names)
project.team.users.get_ids_by_ids_or_usernames(user_ids, user_names)
end
def groups_ids(group_ids, group_paths)
Group.unscoped.public_to_user(author).get_ids_by_ids_or_paths(group_ids, group_paths)
end
end end
end end
end end
...@@ -315,10 +315,11 @@ ...@@ -315,10 +315,11 @@
"type": "array", "type": "array",
"additionalItems": false, "additionalItems": false,
"items": { "items": {
"required": [ "anyOf":[
"type", {"required": ["type","approvals_required","user_approvers"]},
"approvals_required", {"required": ["type","approvals_required","user_approvers_ids"]},
"approvers" {"required": ["type","approvals_required","group_approvers"]},
{"required": ["type","approvals_required","group_approvers_ids"]}
], ],
"type": "object", "type": "object",
"properties": { "properties": {
...@@ -331,13 +332,37 @@ ...@@ -331,13 +332,37 @@
"approvals_required": { "approvals_required": {
"type": "integer" "type": "integer"
}, },
"approvers": { "user_approvers": {
"type": "array",
"additionalItems": false,
"items": {
"minLength": 1,
"type": "string"
}
},
"user_approvers_ids": {
"type": "array",
"additionalItems": false,
"items": {
"minLength": 1,
"type": "integer"
}
},
"group_approvers": {
"type": "array", "type": "array",
"additionalItems": false, "additionalItems": false,
"items": { "items": {
"minLength": 1, "minLength": 1,
"type": "string" "type": "string"
} }
},
"group_approvers_ids": {
"type": "array",
"additionalItems": false,
"items": {
"minLength": 1,
"type": "integer"
}
} }
} }
} }
......
...@@ -54,7 +54,7 @@ FactoryBot.define do ...@@ -54,7 +54,7 @@ FactoryBot.define do
] ]
end end
actions { [{ type: 'require_approval', approvals_required: 1, approvers: %w[admin] }] } actions { [{ type: 'require_approval', approvals_required: 1, user_approvers: %w[admin] }] }
end end
factory :orchestration_policy_yaml, class: Struct.new(:scan_execution_policy, :scan_result_policy) do factory :orchestration_policy_yaml, class: Struct.new(:scan_execution_policy, :scan_result_policy) do
......
...@@ -6,13 +6,15 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessScanResultPolicyS ...@@ -6,13 +6,15 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessScanResultPolicyS
describe '#execute' do describe '#execute' do
let_it_be(:policy_configuration) { create(:security_orchestration_policy_configuration) } let_it_be(:policy_configuration) { create(:security_orchestration_policy_configuration) }
let(:approver) { create(:user) } let(:group) { create(:group, :public) }
let(:policy) { build(:scan_result_policy, name: 'Test Policy') } let(:policy) { build(:scan_result_policy, name: 'Test Policy') }
let(:policy_yaml) { Gitlab::Config::Loader::Yaml.new(policy.to_yaml).load! } let(:policy_yaml) { Gitlab::Config::Loader::Yaml.new(policy.to_yaml).load! }
let(:project) { policy_configuration.project } let(:project) { policy_configuration.project }
let(:approver) { project.owner }
let(:service) { described_class.new(policy_configuration: policy_configuration, policy: policy, policy_index: 0) } let(:service) { described_class.new(policy_configuration: policy_configuration, policy: policy, policy_index: 0) }
before do before do
group.add_maintainer(approver)
allow(policy_configuration).to receive(:policy_last_updated_by).and_return(project.owner) allow(policy_configuration).to receive(:policy_last_updated_by).and_return(project.owner)
end end
...@@ -44,8 +46,35 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessScanResultPolicyS ...@@ -44,8 +46,35 @@ RSpec.describe Security::SecurityOrchestrationPolicies::ProcessScanResultPolicyS
end end
end end
it 'creates a new project approval rule' do shared_examples 'create approval rule with specific approver' do
expect { subject }.to change { project.approval_rules.count }.by(1) it 'succeeds creating approval rules with specific approver' do
expect { subject }.to change { project.approval_rules.count }.by(1)
expect(project.approval_rules.first.approvers).to contain_exactly(approver)
end
end
context 'with only user id' do
let(:policy) { build(:scan_result_policy, name: 'Test Policy', actions: [{ type: 'require_approval', approvals_required: 1, user_approvers_ids: [approver.id] }]) }
it_behaves_like 'create approval rule with specific approver'
end
context 'with only username' do
let(:policy) { build(:scan_result_policy, name: 'Test Policy', actions: [{ type: 'require_approval', approvals_required: 1, user_approvers: [approver.username] }]) }
it_behaves_like 'create approval rule with specific approver'
end
context 'with only group id' do
let(:policy) { build(:scan_result_policy, name: 'Test Policy', actions: [{ type: 'require_approval', approvals_required: 1, group_approvers_ids: [group.id] }]) }
it_behaves_like 'create approval rule with specific approver'
end
context 'with only group path' do
let(:policy) { build(:scan_result_policy, name: 'Test Policy', actions: [{ type: 'require_approval', approvals_required: 1, group_approvers: [group.path] }]) }
it_behaves_like 'create approval rule with specific approver'
end end
it 'sets project approval rules names based on policy name', :aggregate_failures do it 'sets project approval rules names based on policy name', :aggregate_failures do
......
...@@ -48,7 +48,7 @@ RSpec.describe Security::CreateOrchestrationPolicyWorker do ...@@ -48,7 +48,7 @@ RSpec.describe Security::CreateOrchestrationPolicyWorker do
enabled: true, enabled: true,
rules: [{ type: 'scan_finding', branches: %w[production], vulnerabilities_allowed: 0, severity_levels: %w[critical], scanners: %w[container_scanning], vulnerability_states: %w[newly_detected] }], rules: [{ type: 'scan_finding', branches: %w[production], vulnerabilities_allowed: 0, severity_levels: %w[critical], scanners: %w[container_scanning], vulnerability_states: %w[newly_detected] }],
actions: [ actions: [
{ type: 'require_approval', approvals_required: 1, approvers: %w[admin] } { type: 'require_approval', approvals_required: 1, user_approvers: %w[admin] }
] ]
} }
] ]
......
...@@ -680,6 +680,26 @@ RSpec.describe Group do ...@@ -680,6 +680,26 @@ RSpec.describe Group do
expect(result).to match_array([internal_group]) expect(result).to match_array([internal_group])
end end
end end
describe 'by_ids_or_paths' do
let(:group_path) { 'group_path' }
let!(:group) { create(:group, path: group_path) }
let(:group_id) { group.id }
it 'returns matching records based on paths' do
expect(described_class.by_ids_or_paths(nil, [group_path])).to match_array([group])
end
it 'returns matching records based on ids' do
expect(described_class.by_ids_or_paths([group_id], nil)).to match_array([group])
end
it 'returns matching records based on both paths and ids' do
new_group = create(:group)
expect(described_class.by_ids_or_paths([new_group.id], [group_path])).to match_array([group, new_group])
end
end
end end
describe '#to_reference' do describe '#to_reference' do
...@@ -2803,4 +2823,23 @@ RSpec.describe Group do ...@@ -2803,4 +2823,23 @@ RSpec.describe Group do
expect(group.crm_enabled?).to be_truthy expect(group.crm_enabled?).to be_truthy
end end
end end
describe '.get_ids_by_ids_or_paths' do
let(:group_path) { 'group_path' }
let!(:group) { create(:group, path: group_path) }
let(:group_id) { group.id }
it 'returns ids matching records based on paths' do
expect(described_class.get_ids_by_ids_or_paths(nil, [group_path])).to match_array([group_id])
end
it 'returns ids matching records based on ids' do
expect(described_class.get_ids_by_ids_or_paths([group_id], nil)).to match_array([group_id])
end
it 'returns ids matching records based on both paths and ids' do
new_group_id = create(:group).id
expect(described_class.get_ids_by_ids_or_paths([new_group_id], [group_path])).to match_array([group_id, new_group_id])
end
end
end end
...@@ -6458,13 +6458,43 @@ RSpec.describe User do ...@@ -6458,13 +6458,43 @@ RSpec.describe User do
specify { is_expected.to contain_exactly(developer_group2) } specify { is_expected.to contain_exactly(developer_group2) }
end end
describe '.get_ids_by_username' do describe '.get_ids_by_ids_or_usernames' do
let(:user_name) { 'user_name' } let(:user_name) { 'user_name' }
let!(:user) { create(:user, username: user_name) } let!(:user) { create(:user, username: user_name) }
let(:user_id) { user.id } let(:user_id) { user.id }
it 'returns the id of each record matching username' do it 'returns the id of each record matching username' do
expect(described_class.get_ids_by_username([user_name])).to match_array([user_id]) expect(described_class.get_ids_by_ids_or_usernames(nil, [user_name])).to match_array([user_id])
end
it 'returns the id of each record matching user id' do
expect(described_class.get_ids_by_ids_or_usernames([user_id], nil)).to match_array([user_id])
end
it 'return the id for all records matching either user id or user name' do
new_user_id = create(:user).id
expect(described_class.get_ids_by_ids_or_usernames([new_user_id], [user_name])).to match_array([user_id, new_user_id])
end
end
describe '.by_ids_or_usernames' do
let(:user_name) { 'user_name' }
let!(:user) { create(:user, username: user_name) }
let(:user_id) { user.id }
it 'returns matching records based on username' do
expect(described_class.by_ids_or_usernames(nil, [user_name])).to match_array([user])
end
it 'returns matching records based on id' do
expect(described_class.by_ids_or_usernames([user_id], nil)).to match_array([user])
end
it 'returns matching records based on both username and id' do
new_user = create(:user)
expect(described_class.by_ids_or_usernames([new_user.id], [user_name])).to match_array([user, new_user])
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