Commit 533ea2dc authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Olena Horal-Koretska

Limit changing security policy project to project owners

parent 90aff383
...@@ -191,6 +191,10 @@ changes. ...@@ -191,6 +191,10 @@ changes.
You can always change the **Security policy project** by navigating to your project's You can always change the **Security policy project** by navigating to your project's
**Security & Compliance > Policies** and modifying the selected project. **Security & Compliance > Policies** and modifying the selected project.
NOTE:
Only project Owners have the [permissions](../../permissions.md#project-members-permissions)
to select Security Policy Project.
## Roadmap ## Roadmap
See the [Category Direction page](https://about.gitlab.com/direction/protect/container_network_security/) See the [Category Direction page](https://about.gitlab.com/direction/protect/container_network_security/)
......
...@@ -169,6 +169,8 @@ The following table lists project permissions available for each role: ...@@ -169,6 +169,8 @@ The following table lists project permissions available for each role:
| Manage Project Operations | | | | ✓ | ✓ | | Manage Project Operations | | | | ✓ | ✓ |
| Manage Terraform state | | | | ✓ | ✓ | | Manage Terraform state | | | | ✓ | ✓ |
| Manage license policy **(ULTIMATE)** | | | | ✓ | ✓ | | Manage license policy **(ULTIMATE)** | | | | ✓ | ✓ |
| Manage security policy **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| Create or assign security policy project **(ULTIMATE)** | | | | | ✓ |
| Edit comments (posted by any user) | | | | ✓ | ✓ | | Edit comments (posted by any user) | | | | ✓ | ✓ |
| Reposition comments on images (posted by any user)|✓ (*10*) | ✓ (*10*) | ✓ (*10*) | ✓ | ✓ | | Reposition comments on images (posted by any user)|✓ (*10*) | ✓ (*10*) | ✓ (*10*) | ✓ | ✓ |
| Manage Error Tracking | | | | ✓ | ✓ | | Manage Error Tracking | | | | ✓ | ✓ |
......
...@@ -5,9 +5,12 @@ module Projects ...@@ -5,9 +5,12 @@ module Projects
class PoliciesController < Projects::ApplicationController class PoliciesController < Projects::ApplicationController
include SecurityAndCompliancePermissions include SecurityAndCompliancePermissions
before_action :authorize_security_orchestration_policies!
before_action :authorize_update_security_orchestration_policy_project!, only: [:assign]
before_action do before_action do
push_frontend_feature_flag(:security_orchestration_policies_configuration, project) push_frontend_feature_flag(:security_orchestration_policies_configuration, project)
check_permissions! check_feature_flag!
end end
feature_category :security_orchestration feature_category :security_orchestration
...@@ -32,8 +35,8 @@ module Projects ...@@ -32,8 +35,8 @@ module Projects
private private
def check_permissions! def check_feature_flag!
render_404 unless Feature.enabled?(:security_orchestration_policies_configuration, project) && can?(current_user, :security_orchestration_policies, project) render_404 if Feature.disabled?(:security_orchestration_policies_configuration, project)
end end
def policy_project_params def policy_project_params
......
...@@ -7,7 +7,7 @@ module Mutations ...@@ -7,7 +7,7 @@ module Mutations
graphql_name 'SecurityPolicyProjectAssign' graphql_name 'SecurityPolicyProjectAssign'
authorize :security_orchestration_policies authorize :update_security_orchestration_policy_project
argument :project_path, GraphQL::ID_TYPE, argument :project_path, GraphQL::ID_TYPE,
required: true, required: true,
......
...@@ -7,7 +7,7 @@ module Mutations ...@@ -7,7 +7,7 @@ module Mutations
graphql_name 'SecurityPolicyProjectCreate' graphql_name 'SecurityPolicyProjectCreate'
authorize :security_orchestration_policies authorize :update_security_orchestration_policy_project
argument :project_path, GraphQL::ID_TYPE, argument :project_path, GraphQL::ID_TYPE,
required: true, required: true,
......
...@@ -195,6 +195,10 @@ module EE ...@@ -195,6 +195,10 @@ module EE
end end
end end
def can_update_security_orchestration_policy_project?(project)
can?(current_user, :update_security_orchestration_policy_project, project)
end
def can_create_feedback?(project, feedback_type) def can_create_feedback?(project, feedback_type)
feedback = Vulnerabilities::Feedback.new(project: project, feedback_type: feedback_type) feedback = Vulnerabilities::Feedback.new(project: project, feedback_type: feedback_type)
can?(current_user, :create_vulnerability_feedback, feedback) can?(current_user, :create_vulnerability_feedback, feedback)
......
...@@ -195,6 +195,10 @@ module EE ...@@ -195,6 +195,10 @@ module EE
enable :security_orchestration_policies enable :security_orchestration_policies
end end
rule { security_orchestration_policies_enabled & can?(:owner_access) }.policy do
enable :update_security_orchestration_policy_project
end
rule { security_dashboard_enabled & can?(:developer_access) }.policy do rule { security_dashboard_enabled & can?(:developer_access) }.policy do
enable :read_security_resource enable :read_security_resource
enable :read_vulnerability_scanner enable :read_vulnerability_scanner
......
...@@ -7,8 +7,11 @@ ...@@ -7,8 +7,11 @@
= s_('SecurityOrchestration|Security policy project') = s_('SecurityOrchestration|Security policy project')
%p %p
= project_select_tag('orchestration[policy_project_id]', class: 'hidden-filter-value', toggle_class: 'js-project-search js-project-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', = project_select_tag('orchestration[policy_project_id]', class: 'hidden-filter-value', toggle_class: 'js-project-search js-project-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
placeholder: _('Select project'), idAttribute: 'id', data: { order_by: 'last_activity_at', idattribute: 'id', simple_filter: true, allow_clear: true, include_groups: false, include_projects_in_subgroups: true, user_id: current_user.id }, value: @assigned_policy_id) placeholder: _('Select project'), idAttribute: 'id', disabled: !can_update_security_orchestration_policy_project?(@project), data: { order_by: 'last_activity_at', idattribute: 'id', simple_filter: true, allow_clear: true, include_groups: false, include_projects_in_subgroups: true, user_id: current_user.id }, value: @assigned_policy_id)
.text-muted .text-muted
= html_escape(s_('SecurityOrchestration|A security policy project can be used enforce policies for a given project, group, or instance. It allows you to specify security policies that are important to you and enforce them with every commit.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe } = html_escape(s_('SecurityOrchestration|A security policy project can be used enforce policies for a given project, group, or instance. It allows you to specify security policies that are important to you and enforce them with every commit.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
= link_to _('More information'), help_page_path('user/project/clusters/protect/container_network_security/quick_start_guide'), target: '_blank' = link_to _('More information'), help_page_path('user/project/clusters/protect/container_network_security/quick_start_guide'), target: '_blank'
= field.submit _('Save changes'), class: 'btn gl-button btn-success' - if can_update_security_orchestration_policy_project?(@project)
= field.submit _('Save changes'), class: 'btn gl-button btn-success'
- else
= field.submit _('Save changes'), class: 'btn gl-button btn-success has-tooltip', disabled: true, title: _('Only owners can update Security Policy Project')
...@@ -3,14 +3,17 @@ ...@@ -3,14 +3,17 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
describe '#resolve' do describe '#resolve' do
let_it_be(:owner) { create(:user) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) } let_it_be(:project) { create(:project, namespace: owner.namespace) }
let_it_be(:policy_project) { create(:project) } let_it_be(:policy_project) { create(:project) }
let_it_be(:policy_project_id) { GitlabSchema.id_from_object(policy_project) } let_it_be(:policy_project_id) { GitlabSchema.id_from_object(policy_project) }
let(:current_user) { owner }
subject { mutation.resolve(project_path: project.full_path, security_policy_project_id: policy_project_id) } subject { mutation.resolve(project_path: project.full_path, security_policy_project_id: policy_project_id) }
context 'when feature is enabled and permission is set for user' do context 'when feature is enabled and permission is set for user' do
...@@ -19,12 +22,26 @@ RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do ...@@ -19,12 +22,26 @@ RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do
stub_feature_flags(security_orchestration_policies_configuration: true) stub_feature_flags(security_orchestration_policies_configuration: true)
end end
it 'assigns the security policy project' do context 'when user is an owner of the project' do
result = subject it 'assigns the security policy project' do
result = subject
expect(result[:errors]).to be_empty
expect(project.security_orchestration_policy_configuration).not_to be_nil
expect(project.security_orchestration_policy_configuration.security_policy_management_project).to eq(policy_project)
end
end
context 'when user is not an owner' do
let(:current_user) { user }
before do
project.add_maintainer(user)
end
expect(result[:errors]).to be_empty it 'raises exception' do
expect(project.security_orchestration_policy_configuration).not_to be_nil expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect(project.security_orchestration_policy_configuration.security_policy_management_project).to eq(policy_project) end
end end
end end
...@@ -47,7 +64,7 @@ RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do ...@@ -47,7 +64,7 @@ RSpec.describe Mutations::SecurityPolicy::AssignSecurityPolicyProject do
end end
end end
context 'when permission is not enabled' do context 'when feature is not licensed' do
before do before do
stub_licensed_features(security_orchestration_policies: false) stub_licensed_features(security_orchestration_policies: false)
end end
......
...@@ -2,27 +2,44 @@ ...@@ -2,27 +2,44 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject do RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject do
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
describe '#resolve' do describe '#resolve' do
let_it_be(:owner) { create(:user) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) } let_it_be(:project) { create(:project, namespace: owner.namespace) }
let(:current_user) { owner }
subject { mutation.resolve(project_path: project.full_path) } subject { mutation.resolve(project_path: project.full_path) }
context 'when feature is enabled and permission is set for user' do context 'when feature is enabled and permission is set for user' do
before do before do
project.add_maintainer(user)
stub_licensed_features(security_orchestration_policies: true) stub_licensed_features(security_orchestration_policies: true)
stub_feature_flags(security_orchestration_policies_configuration: true) stub_feature_flags(security_orchestration_policies_configuration: true)
end end
it 'returns project' do context 'when user is an owner of the project' do
result = subject let(:current_user) { owner }
it 'returns project' do
result = subject
expect(result[:errors]).to be_empty
expect(result[:project]).to eq(Project.last)
end
end
context 'when user is not an owner' do
let(:current_user) { user }
before do
project.add_maintainer(user)
end
expect(result[:errors]).to be_empty it 'raises exception' do
expect(result[:project]).to eq(Project.last) expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end end
end end
...@@ -37,7 +54,7 @@ RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject do ...@@ -37,7 +54,7 @@ RSpec.describe Mutations::SecurityPolicy::CreateSecurityPolicyProject do
end end
end end
context 'when permission is not enabled' do context 'when feature is not licensed' do
before do before do
stub_licensed_features(security_orchestration_policies: false) stub_licensed_features(security_orchestration_policies: false)
end end
......
...@@ -20,6 +20,24 @@ RSpec.describe ProjectsHelper do ...@@ -20,6 +20,24 @@ RSpec.describe ProjectsHelper do
end end
end end
describe '#can_update_security_orchestration_policy_project?' do
let(:owner) { project.owner }
before do
allow(helper).to receive(:current_user) { owner }
end
it 'returns false when user cannot update security orchestration policy project' do
allow(helper).to receive(:can?).with(owner, :update_security_orchestration_policy_project, project) { false }
expect(helper.can_update_security_orchestration_policy_project?(project)).to eq false
end
it 'returns true when user can update security orchestration policy project' do
allow(helper).to receive(:can?).with(owner, :update_security_orchestration_policy_project, project) { true }
expect(helper.can_update_security_orchestration_policy_project?(project)).to eq true
end
end
describe '#can_import_members?' do describe '#can_import_members?' do
let(:owner) { project.owner } let(:owner) { project.owner }
......
...@@ -748,13 +748,25 @@ RSpec.describe ProjectPolicy do ...@@ -748,13 +748,25 @@ RSpec.describe ProjectPolicy do
stub_licensed_features(security_orchestration_policies: true) stub_licensed_features(security_orchestration_policies: true)
end end
context 'with developer or higher role' do context 'with developer or maintainer role' do
where(role: %w[owner maintainer developer]) where(role: %w[maintainer developer])
with_them do with_them do
let(:current_user) { public_send(role) } let(:current_user) { public_send(role) }
it { is_expected.to be_allowed(:security_orchestration_policies) } it { is_expected.to be_allowed(:security_orchestration_policies) }
it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) }
end
end
context 'with owner role' do
where(role: %w[owner])
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to be_allowed(:security_orchestration_policies) }
it { is_expected.to be_allowed(:update_security_orchestration_policy_project) }
end end
end end
end end
......
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Projects::Security::PoliciesController, type: :request do RSpec.describe Projects::Security::PoliciesController, type: :request do
let_it_be(:project, reload: true) { create(:project) } let_it_be(:owner) { create(:user) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, namespace: owner.namespace) }
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -45,17 +46,32 @@ RSpec.describe Projects::Security::PoliciesController, type: :request do ...@@ -45,17 +46,32 @@ RSpec.describe Projects::Security::PoliciesController, type: :request do
stub_licensed_features(security_orchestration_policies: true) stub_licensed_features(security_orchestration_policies: true)
end end
it 'assigns policy project to project' do context 'when user is not an owner of the project' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: policy_project.id } } it 'returns error message' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: policy_project.id } }
expect(response).to redirect_to(project_security_policy_url(project)) expect(response).to have_gitlab_http_status(:not_found)
expect(project.security_orchestration_policy_configuration.security_policy_management_project_id).to eq(policy_project.id) expect(response).not_to render_template('new')
end
end end
it 'returns error message for invalid input' do context 'when user is an owner of the project' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: nil } } before do
login_as(owner)
end
it 'assigns policy project to project' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: policy_project.id } }
expect(flash[:alert]).to eq 'Policy project doesn\'t exist' expect(response).to redirect_to(project_security_policy_url(project))
expect(project.security_orchestration_policy_configuration.security_policy_management_project_id).to eq(policy_project.id)
end
it 'returns error message for invalid input' do
post assign_project_security_policy_url(project), params: { orchestration: { policy_project_id: nil } }
expect(flash[:alert]).to eq 'Policy project doesn\'t exist'
end
end end
end end
end end
...@@ -22940,6 +22940,9 @@ msgstr "" ...@@ -22940,6 +22940,9 @@ msgstr ""
msgid "Only include features new to your current subscription tier." msgid "Only include features new to your current subscription tier."
msgstr "" msgstr ""
msgid "Only owners can update Security Policy Project"
msgstr ""
msgid "Only policy:" msgid "Only policy:"
msgstr "" msgstr ""
......
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