Commit 22570587 authored by Małgorzata Ksionek's avatar Małgorzata Ksionek

Add group push rules feature

Fix failing push_rules_controller spec

Add push rules to nav spec

Fix specs on project_push_rules

Add feature spec for groups

Add modified string file

Update changelog

Fix specs

Add additional check

Remove

bring back

FIX

Add cr remarks

FIX

Fix

Add cr remarks

Add cr remarks

Add cr remarks

Add cr remarks

Add cr remarks

Add cr remarks

Enable admin mode
parent 25f4c592
...@@ -102,6 +102,8 @@ ...@@ -102,6 +102,8 @@
= render_if_exists "layouts/nav/ee/security_link" # EE-specific = render_if_exists "layouts/nav/ee/security_link" # EE-specific
= render_if_exists "layouts/nav/ee/push_rules_link" # EE-specific
- if group_sidebar_link?(:kubernetes) - if group_sidebar_link?(:kubernetes)
= nav_link(controller: [:clusters]) do = nav_link(controller: [:clusters]) do
= link_to group_clusters_path(@group) do = link_to group_clusters_path(@group) do
......
# frozen_string_literal: true
class Groups::PushRulesController < Groups::ApplicationController
include Gitlab::Utils::StrongMemoize
include PushRulesHelper
layout 'group'
before_action :check_push_rules_available!
before_action :push_rule
respond_to :html
def edit
end
def update
if @push_rule.update(push_rule_params)
flash[:notice] = _('Push Rule updated successfully.')
else
flash[:alert] = @push_rule.errors.full_messages.join(', ').html_safe
end
redirect_to edit_group_push_rules_path(group)
end
private
def push_rule_params
allowed_fields = %i[deny_delete_tag delete_branch_regex commit_message_regex commit_message_negative_regex
branch_name_regex force_push_regex author_email_regex
member_check file_name_regex max_file_size prevent_secrets]
if can?(current_user, :change_reject_unsigned_commits, group)
allowed_fields << :reject_unsigned_commits
end
if can?(current_user, :change_commit_committer_check, group)
allowed_fields << :commit_committer_check
end
params.require(:push_rule).permit(allowed_fields)
end
def push_rule
strong_memoize(:push_rule) do
group.update(push_rule: PushRule.create!) unless group.push_rule
group.push_rule
end
end
def check_push_rules_available!
render_404 unless can_modify_group_push_rules?(current_user, group)
end
end
...@@ -137,10 +137,10 @@ module EE ...@@ -137,10 +137,10 @@ module EE
::Gitlab.config.alternative_gitlab_kerberos_url? ::Gitlab.config.alternative_gitlab_kerberos_url?
end end
def can_change_push_rule?(push_rule, rule) def can_change_push_rule?(push_rule, rule, context)
return true if push_rule.global? return true if push_rule.global?
can?(current_user, :"change_#{rule}", @project) can?(current_user, :"change_#{rule}", context)
end end
def ci_cd_projects_available? def ci_cd_projects_available?
......
# frozen_string_literal: true # frozen_string_literal: true
module PushRulesHelper module PushRulesHelper
def can_modify_group_push_rules?(current_user, group)
can?(current_user, :change_push_rules, group)
end
def reject_unsigned_commits_description(push_rule) def reject_unsigned_commits_description(push_rule)
message = s_("ProjectSettings|Only signed commits can be pushed to this repository.") message = s_("ProjectSettings|Only signed commits can be pushed to this repository.")
......
...@@ -351,9 +351,9 @@ module EE ...@@ -351,9 +351,9 @@ module EE
def predefined_push_rule def predefined_push_rule
strong_memoize(:predefined_push_rule) do strong_memoize(:predefined_push_rule) do
if push_rule.present? next push_rule if push_rule
push_rule
elsif has_parent? if has_parent?
parent.predefined_push_rule parent.predefined_push_rule
else else
PushRule.global PushRule.global
......
...@@ -21,7 +21,6 @@ class PushRule < ApplicationRecord ...@@ -21,7 +21,6 @@ class PushRule < ApplicationRecord
belongs_to :project belongs_to :project
validates :project, presence: true, unless: :is_sample?
validates :max_file_size, numericality: { greater_than_or_equal_to: 0, only_integer: true } validates :max_file_size, numericality: { greater_than_or_equal_to: 0, only_integer: true }
validates(*REGEX_COLUMNS, untrusted_regexp: true) validates(*REGEX_COLUMNS, untrusted_regexp: true)
...@@ -91,11 +90,12 @@ class PushRule < ApplicationRecord ...@@ -91,11 +90,12 @@ class PushRule < ApplicationRecord
is_sample? is_sample?
end end
def available?(feature_sym) def available?(feature_sym, object: nil)
if global? if global?
License.feature_available?(feature_sym) License.feature_available?(feature_sym)
else else
project&.feature_available?(feature_sym) object ||= project
object&.feature_available?(feature_sym)
end end
end end
......
...@@ -70,6 +70,28 @@ module EE ...@@ -70,6 +70,28 @@ module EE
License.feature_available?(:cluster_health) License.feature_available?(:cluster_health)
end end
with_scope :global
condition(:commit_committer_check_disabled_globally) do
!PushRule.global&.commit_committer_check
end
with_scope :global
condition(:reject_unsigned_commits_disabled_globally) do
!PushRule.global&.reject_unsigned_commits
end
condition(:commit_committer_check_available) do
@subject.feature_available?(:commit_committer_check)
end
condition(:reject_unsigned_commits_available) do
@subject.feature_available?(:reject_unsigned_commits)
end
condition(:push_rules_available) do
::Feature.enabled?(:group_push_rules, @subject.root_ancestor) && @subject.feature_available?(:push_rules)
end
rule { public_group | logged_in_viewable }.policy do rule { public_group | logged_in_viewable }.policy do
enable :read_wiki enable :read_wiki
enable :download_wiki_code enable :download_wiki_code
...@@ -212,6 +234,26 @@ module EE ...@@ -212,6 +234,26 @@ module EE
prevent(*create_read_update_admin_destroy(:wiki)) prevent(*create_read_update_admin_destroy(:wiki))
prevent(:download_wiki_code) prevent(:download_wiki_code)
end end
rule { admin | (commit_committer_check_disabled_globally & can?(:maintainer_access)) }.policy do
enable :change_commit_committer_check
end
rule { commit_committer_check_available }.policy do
enable :read_commit_committer_check
end
rule { ~commit_committer_check_available }.policy do
prevent :change_commit_committer_check
end
rule { admin | (reject_unsigned_commits_disabled_globally & can?(:maintainer_access)) }.enable :change_reject_unsigned_commits
rule { reject_unsigned_commits_available }.enable :read_reject_unsigned_commits
rule { ~reject_unsigned_commits_available }.prevent :change_reject_unsigned_commits
rule { can?(:maintainer_access) & push_rules_available }.enable :change_push_rules
end end
override :lookup_access_level! override :lookup_access_level!
......
...@@ -80,6 +80,34 @@ module EE ...@@ -80,6 +80,34 @@ module EE
License.feature_available?(:cluster_health) License.feature_available?(:cluster_health)
end end
with_scope :subject
condition(:group_push_rules_enabled) do
@subject.group && ::Feature.enabled?(:group_push_rules, @subject.group.root_ancestor)
end
with_scope :subject
condition(:group_push_rule_present) do
group_push_rules_enabled? && subject.group.push_rule
end
with_scope :subject
condition(:reject_unsigned_commits_disabled_by_group) do
if group_push_rule_present?
!subject.group.push_rule.reject_unsigned_commits
else
true
end
end
with_scope :subject
condition(:commit_committer_check_disabled_by_group) do
if group_push_rule_present?
!subject.group.push_rule.commit_committer_check
else
true
end
end
with_scope :subject with_scope :subject
condition(:commit_committer_check_available) do condition(:commit_committer_check_available) do
@subject.feature_available?(:commit_committer_check) @subject.feature_available?(:commit_committer_check)
...@@ -273,13 +301,13 @@ module EE ...@@ -273,13 +301,13 @@ module EE
rule { ~can?(:push_code) }.prevent :push_code_to_protected_branches rule { ~can?(:push_code) }.prevent :push_code_to_protected_branches
rule { admin | (reject_unsigned_commits_disabled_globally & can?(:maintainer_access)) }.enable :change_reject_unsigned_commits rule { admin | (reject_unsigned_commits_disabled_globally & reject_unsigned_commits_disabled_by_group & can?(:maintainer_access)) }.enable :change_reject_unsigned_commits
rule { reject_unsigned_commits_available }.enable :read_reject_unsigned_commits rule { reject_unsigned_commits_available }.enable :read_reject_unsigned_commits
rule { ~reject_unsigned_commits_available }.prevent :change_reject_unsigned_commits rule { ~reject_unsigned_commits_available }.prevent :change_reject_unsigned_commits
rule { admin | (commit_committer_check_disabled_globally & can?(:maintainer_access)) }.policy do rule { admin | (commit_committer_check_disabled_globally & commit_committer_check_disabled_by_group & can?(:maintainer_access)) }.policy do
enable :change_commit_committer_check enable :change_commit_committer_check
end end
......
...@@ -7,7 +7,12 @@ module EE ...@@ -7,7 +7,12 @@ module EE
override :execute override :execute
def execute def execute
super.tap { |group| log_audit_event if group&.persisted? } super.tap do |group|
next unless group&.persisted?
log_audit_event
create_push_rule_for_group
end
end end
private private
...@@ -36,6 +41,18 @@ module EE ...@@ -36,6 +41,18 @@ module EE
action: :create action: :create
).for_group.security_event ).for_group.security_event
end end
def create_push_rule_for_group
return unless ::Feature.enabled?(:group_push_rules, group.root_ancestor) && group.feature_available?(:push_rules)
push_rule = group.predefined_push_rule
return unless push_rule
attributes = push_rule.attributes.symbolize_keys.except(:is_sample, :id)
PushRule.create(attributes.compact).tap do |push_rule|
group.update(push_rule: push_rule)
end
end
end end
end end
end end
...@@ -56,13 +56,29 @@ module EE ...@@ -56,13 +56,29 @@ module EE
predefined_push_rule = PushRule.find_by(is_sample: true) predefined_push_rule = PushRule.find_by(is_sample: true)
if predefined_push_rule if group_push_rule_available?
create_push_rule_from_group
elsif predefined_push_rule
log_info(predefined_push_rule)
push_rule = predefined_push_rule.dup.tap { |gh| gh.is_sample = false } push_rule = predefined_push_rule.dup.tap { |gh| gh.is_sample = false }
project.push_rule = push_rule project.push_rule = push_rule
project.project_setting.update(push_rule: push_rule) project.project_setting.update(push_rule: push_rule)
end end
end end
def group_push_rule_available?
return false unless project.group
return false unless ::Feature.enabled?(:group_push_rules, project.group.root_ancestor)
!!project.group.predefined_push_rule
end
def create_push_rule_from_group
push_rule_attributes = project.group.predefined_push_rule.attributes.except("id")
project.create_push_rule(push_rule_attributes.merge(is_sample: false))
project.project_setting.update(push_rule: project.push_rule)
end
# When using a project template from a Group, the new project can only be created # When using a project template from a Group, the new project can only be created
# under the template owner's group or subgroups # under the template owner's group or subgroups
def validate_namespace_used_with_template(project, group_with_project_templates_id) def validate_namespace_used_with_template(project, group_with_project_templates_id)
......
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
.alert.alert-danger .alert.alert-danger
- @push_rule.errors.full_messages.each do |msg| - @push_rule.errors.full_messages.each do |msg|
%p= msg %p= msg
= render "shared/push_rules/form", f: f = render "shared/push_rules/form", f: f, context: nil
- page_title "Push Rules"
%h3
= _("Pre-defined push rules.")
%p.light
= _("Rules that define what git pushes are accepted for a project in this group. All newly created projects in this group will use these settings.")
%hr.clearfix
= form_for @push_rule, url: group_push_rules_path(@group), as: :push_rule, method: :put do |f|
- if @push_rule.errors.any?
.alert.alert-danger
- @push_rule.errors.full_messages.each do |msg|
%p= msg
= render "shared/push_rules/form", f: f, context: @group
- return unless can_modify_group_push_rules?(current_user, @group)
= nav_link(controller: :push_rules) do
= link_to edit_group_push_rules_path(@group), title: _('Push Rules') do
.nav-icon-container
= sprite_icon('push-rules')
%span.nav-item-name
= _('Push Rules')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :push_rules, html_options: { class: "fly-out-top-item" } ) do
= link_to edit_group_push_rules_path(@group), title: _('Push Rules') do
%strong.fly-out-top-item-name
= _('Push Rules')
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
= form_for [@project.namespace.becomes(Namespace), @project, @push_rule] do |f| = form_for [@project.namespace.becomes(Namespace), @project, @push_rule] do |f|
= form_errors(@push_rule) = form_errors(@push_rule)
= render "shared/push_rules/form", f: f = render "shared/push_rules/form", f: f, context: @project
- return unless push_rule.available?(:commit_committer_check) - context = local_assigns.fetch(:context)
- return unless push_rule.available?(:commit_committer_check, object: context)
- form = local_assigns.fetch(:form) - form = local_assigns.fetch(:form)
- push_rule = local_assigns.fetch(:push_rule) - push_rule = local_assigns.fetch(:push_rule)
.form-check .form-check
= form.check_box :commit_committer_check, class: "form-check-input", disabled: !can_change_push_rule?(form.object, :commit_committer_check), data: { qa_selector: 'committer_restriction_checkbox' } = form.check_box :commit_committer_check, class: "form-check-input", disabled: !can_change_push_rule?(form.object, :commit_committer_check, context), data: { qa_selector: 'committer_restriction_checkbox' }
= form.label :commit_committer_check, class: "label-bold form-check-label" do = form.label :commit_committer_check, class: "label-bold form-check-label" do
= s_("PushRule|Committer restriction") = s_("PushRule|Committer restriction")
%p.text-muted %p.text-muted
......
= render 'shared/push_rules/commit_committer_check_setting', form: f, push_rule: f.object = render 'shared/push_rules/commit_committer_check_setting', form: f, push_rule: f.object, context: context
= render 'shared/push_rules/reject_unsigned_commits_setting', form: f, push_rule: f.object = render 'shared/push_rules/reject_unsigned_commits_setting', form: f, push_rule: f.object, context: context
.form-check .form-check
= f.check_box :deny_delete_tag, class: "form-check-input", data: { qa_selector: 'deny_delete_tag_checkbox' } = f.check_box :deny_delete_tag, class: "form-check-input", data: { qa_selector: 'deny_delete_tag_checkbox' }
......
- return unless push_rule.available?(:reject_unsigned_commits) - context = local_assigns.fetch(:context)
- return unless push_rule.available?(:reject_unsigned_commits, object: context)
- form = local_assigns.fetch(:form) - form = local_assigns.fetch(:form)
- push_rule = local_assigns.fetch(:push_rule) - push_rule = local_assigns.fetch(:push_rule)
.form-check .form-check
= form.check_box :reject_unsigned_commits, class: "form-check-input", disabled: !can_change_push_rule?(form.object, :reject_unsigned_commits), data: { qa_selector: 'reject_unsigned_commits_checkbox' } = form.check_box :reject_unsigned_commits, class: "form-check-input", disabled: !can_change_push_rule?(form.object, :reject_unsigned_commits, context), data: { qa_selector: 'reject_unsigned_commits_checkbox' }
= form.label :reject_unsigned_commits, class: "label-bold form-check-label" do = form.label :reject_unsigned_commits, class: "label-bold form-check-label" do
Reject unsigned commits Reject unsigned commits
%p.text-muted %p.text-muted
......
---
title: Add push rules configuration for groups - frontend
merge_request: 31085
author:
type: added
...@@ -146,6 +146,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -146,6 +146,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
end end
end end
resource :push_rules, only: [:edit, :update]
resource :saml_providers, path: 'saml', only: [:show, :create, :update] do resource :saml_providers, path: 'saml', only: [:show, :create, :update] do
callback_methods = Rails.env.test? ? [:get, :post] : [:post] callback_methods = Rails.env.test? ? [:get, :post] : [:post]
match :callback, to: 'omniauth_callbacks#group_saml', via: callback_methods match :callback, to: 'omniauth_callbacks#group_saml', via: callback_methods
......
# frozen_string_literal: true
require 'spec_helper'
describe Groups::PushRulesController do
let_it_be(:group) { create(:group, :private) }
let_it_be(:user) { create(:user) }
describe '#show' do
context 'when user is at least a maintainer' do
before do
sign_in(user)
group.add_maintainer(user)
end
context 'when push rules feature is disabled' do
before do
stub_licensed_features(push_rules: false)
end
it 'returns 404 status' do
get :edit, params: { group_id: group }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when push rules feature is enabled' do
before do
stub_licensed_features(push_rules: true)
end
it 'returns 200 status' do
get :edit, params: { group_id: group }
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'when user role is lower than maintainer' do
before do
sign_in(user)
group.add_developer(user)
end
context 'when push rules feature is disabled' do
before do
stub_licensed_features(push_rules: false)
end
it 'returns 404 status' do
get :edit, params: { group_id: group }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when push rules feature is enabled' do
before do
stub_licensed_features(push_rules: true)
end
it 'returns 404 status' do
get :edit, params: { group_id: group }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
describe '#update' do
def do_update
patch :update, params: { group_id: group, push_rule: { prevent_secrets: true } }
end
context 'when user is at least a maintainer' do
before do
sign_in(user)
group.add_maintainer(user)
end
context 'push rules unlicensed' do
before do
stub_licensed_features(push_rules: false)
end
it 'returns 404 status' do
do_update
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'push rules licensed', :enable_admin_mode do
before do
stub_licensed_features(push_rules: true)
end
it 'updates the push rule' do
do_update
expect(response).to have_gitlab_http_status(:found)
expect(group.reload.push_rule.prevent_secrets).to be_truthy
end
shared_examples 'updateable setting' do |rule_attr, new_value|
it 'updates the setting' do
patch :update, params: { group_id: group, push_rule: { rule_attr => new_value } }
expect(group.reload.push_rule.public_send(rule_attr)).to eq(new_value)
end
end
shared_examples 'not updateable setting' do |rule_attr, new_value|
it 'does not update the setting' do
expect do
patch :update, params: { group_id: group, push_rule: { rule_attr => new_value } }
end.not_to change { group.reload.push_rule.public_send(rule_attr) }
end
end
shared_examples 'an updatable setting with global default' do |rule_attr|
context "when #{rule_attr} not specified on global level" do
before do
stub_licensed_features(rule_attr => true)
end
it_behaves_like 'updateable setting', rule_attr, true
end
context "when global setting #{rule_attr} is enabled" do
before do
stub_licensed_features(rule_attr => true)
create(:push_rule_sample, rule_attr => true)
end
it_behaves_like 'updateable setting', rule_attr, true
end
end
shared_examples 'a not updatable setting with global default' do |rule_attr|
context "when #{rule_attr} is disabled" do
before do
stub_licensed_features(rule_attr => false)
end
it_behaves_like 'not updateable setting', rule_attr, true
end
context "when global setting #{rule_attr} is enabled" do
before do
stub_licensed_features(rule_attr => true)
create(:push_rule_sample, rule_attr => true)
end
it_behaves_like 'not updateable setting', rule_attr, true
end
end
PushRule::SETTINGS_WITH_GLOBAL_DEFAULT.each do |rule_attr|
context "Updating #{rule_attr} rule" do
let(:push_rule_for_group) { create(:push_rule, rule_attr => false) }
before do
group.update!(push_rule_id: push_rule_for_group.id)
end
context 'as an admin' do
let(:user) { create(:admin) }
it_behaves_like 'an updatable setting with global default', rule_attr, updates: true
end
context 'as a maintainer user' do
before do
group.add_maintainer(user)
end
context "when global setting #{rule_attr} is disabled" do
before do
stub_licensed_features(rule_attr => false)
create(:push_rule_sample, rule_attr => true)
end
it_behaves_like 'updateable setting', rule_attr, true
end
context "when global setting #{rule_attr} is enabled" do
before do
stub_licensed_features(rule_attr => true)
create(:push_rule_sample, rule_attr => true)
end
it_behaves_like 'not updateable setting', rule_attr, true
end
end
context 'as a developer user' do
before do
group.add_developer(user)
end
it_behaves_like 'a not updatable setting with global default', rule_attr
end
end
end
end
end
context 'when user role is lower than maintainer' do
before do
sign_in(user)
group.add_developer(user)
end
context 'push rules unlicensed' do
before do
stub_licensed_features(push_rules: false)
end
it 'returns 404 status' do
do_update
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'push rules licensed' do
before do
stub_licensed_features(push_rules: true)
end
it 'returns 404 status' do
do_update
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
end
...@@ -20,5 +20,9 @@ FactoryBot.define do ...@@ -20,5 +20,9 @@ FactoryBot.define do
factory :push_rule_sample do factory :push_rule_sample do
is_sample { true } is_sample { true }
end end
factory :push_rule_without_project do
project { nil }
end
end end
end end
...@@ -13,6 +13,7 @@ describe 'Group navbar' do ...@@ -13,6 +13,7 @@ describe 'Group navbar' do
before do before do
group.add_maintainer(user) group.add_maintainer(user)
stub_feature_flags(group_push_rules: false)
sign_in(user) sign_in(user)
end end
...@@ -154,7 +155,6 @@ describe 'Group navbar' do ...@@ -154,7 +155,6 @@ describe 'Group navbar' do
nav_sub_items: [_('Package Registry')] nav_sub_items: [_('Package Registry')]
} }
) )
visit group_path(group) visit group_path(group)
end end
...@@ -176,4 +176,27 @@ describe 'Group navbar' do ...@@ -176,4 +176,27 @@ describe 'Group navbar' do
it_behaves_like 'verified navigation bar' it_behaves_like 'verified navigation bar'
end end
end end
context 'when push_rules for groups are available' do
before do
group.add_owner(user)
stub_feature_flags(group_push_rules: true)
insert_after_nav_item(
_('Merge Requests'),
new_nav_item: {
nav_item: _('Push Rules'),
nav_sub_items: []
}
)
insert_after_nav_item(_('Members'), new_nav_item: settings_nav_item)
insert_after_nav_item(_('Settings'), new_nav_item: administration_nav_item)
visit group_path(group)
end
it_behaves_like 'verified navigation bar'
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe 'Groups > Push Rules', :js do
let_it_be(:user) { create(:user) }
let_it_be(:push_rule) { create(:push_rule_without_project) }
let_it_be(:group) { create(:group, push_rule: push_rule) }
before do
group.add_maintainer(user)
sign_in(user)
end
push_rules_with_titles = {
reject_unsigned_commits: 'Reject unsigned commits',
commit_committer_check: 'Committer restriction'
}
push_rules_with_titles.each do |rule_attr, title|
describe "#{rule_attr} rule" do
context 'unlicensed' do
before do
stub_licensed_features(rule_attr => false)
end
it 'does not render the setting checkbox' do
visit edit_group_push_rules_path(group)
expect(page).not_to have_content(title)
end
end
context 'licensed' do
before do
stub_licensed_features(rule_attr => true)
end
it 'renders the setting checkbox' do
visit edit_group_push_rules_path(group)
expect(page).to have_content(title)
end
describe 'with GL.com plans' do
before do
stub_application_setting(check_namespace_plan: true)
end
context 'when disabled' do
it 'does not render the setting checkbox' do
create(:gitlab_subscription, :bronze, namespace: group)
visit edit_group_push_rules_path(group)
expect(page).not_to have_content(title)
end
end
context 'when enabled' do
it 'renders the setting checkbox' do
create(:gitlab_subscription, :gold, namespace: group)
visit edit_group_push_rules_path(group)
expect(page).to have_content(title)
end
end
end
end
end
end
end
...@@ -15,7 +15,6 @@ describe PushRule do ...@@ -15,7 +15,6 @@ describe PushRule do
end end
describe "Validation" do describe "Validation" do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_numericality_of(:max_file_size).is_greater_than_or_equal_to(0).only_integer } it { is_expected.to validate_numericality_of(:max_file_size).is_greater_than_or_equal_to(0).only_integer }
it 'validates RE2 regex syntax' do it 'validates RE2 regex syntax' do
......
...@@ -728,6 +728,115 @@ describe GroupPolicy do ...@@ -728,6 +728,115 @@ describe GroupPolicy do
end end
end end
context 'commit_committer_check is not enabled by the current license' do
before do
stub_licensed_features(commit_committer_check: false)
end
let(:current_user) { maintainer }
it { is_expected.not_to be_allowed(:change_commit_committer_check) }
it { is_expected.not_to be_allowed(:read_commit_committer_check) }
end
context 'commit_committer_check is enabled by the current license' do
before do
stub_licensed_features(commit_committer_check: true)
end
context 'the user is a maintainer' do
let(:current_user) { maintainer }
it { is_expected.to be_allowed(:change_commit_committer_check) }
it { is_expected.to be_allowed(:read_commit_committer_check) }
end
context 'the user is a developer' do
let(:current_user) { developer }
it { is_expected.not_to be_allowed(:change_commit_committer_check) }
it { is_expected.to be_allowed(:read_commit_committer_check) }
end
context 'it is enabled on global level' do
before do
create(:push_rule_sample, commit_committer_check: true)
end
context 'the user is a maintainer' do
let(:current_user) { maintainer }
it { is_expected.not_to be_allowed(:change_commit_committer_check) }
it { is_expected.to be_allowed(:read_commit_committer_check) }
end
context 'the user is a developer' do
let(:current_user) { developer }
it { is_expected.not_to be_allowed(:change_commit_committer_check) }
it { is_expected.to be_allowed(:read_commit_committer_check) }
end
end
end
context 'reject_unsigned_commits is not enabled by the current license' do
before do
stub_licensed_features(reject_unsigned_commits: false)
end
let(:current_user) { maintainer }
it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.not_to be_allowed(:read_reject_unsigned_commits) }
end
context 'reject_unsigned_commits is enabled by the current license' do
before do
stub_licensed_features(reject_unsigned_commits: true)
end
context 'the user is a maintainer' do
let(:current_user) { maintainer }
it { is_expected.to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end
context 'the user is a developer' do
let(:current_user) { developer }
it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end
context 'it is enabled on global level' do
before do
create(:push_rule_sample, reject_unsigned_commits: true)
end
context 'the user is a maintainer' do
let(:current_user) { maintainer }
it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end
context 'the user is a developer' do
let(:current_user) { developer }
it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end
context 'the user is an admin', :enable_admin_mode do
let(:current_user) { admin }
it { is_expected.to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end
end
end
shared_examples 'analytics policy' do |action| shared_examples 'analytics policy' do |action|
shared_examples 'policy by role' do |role| shared_examples 'policy by role' do |role|
context role do context role do
......
...@@ -1126,6 +1126,46 @@ describe ProjectPolicy do ...@@ -1126,6 +1126,46 @@ describe ProjectPolicy do
it { is_expected.not_to be_allowed(:change_commit_committer_check) } it { is_expected.not_to be_allowed(:change_commit_committer_check) }
it { is_expected.to be_allowed(:read_commit_committer_check) } it { is_expected.to be_allowed(:read_commit_committer_check) }
end end
context 'it is enabled on global level' do
before do
create(:push_rule_sample, commit_committer_check: true)
end
context 'when the user is a maintainer' do
let(:current_user) { maintainer }
it { is_expected.not_to be_allowed(:change_commit_committer_check) }
it { is_expected.to be_allowed(:read_commit_committer_check) }
end
context 'when the user is a developer' do
let(:current_user) { developer }
it { is_expected.not_to be_allowed(:change_commit_committer_check) }
it { is_expected.to be_allowed(:read_commit_committer_check) }
end
end
context 'it is enabled on group level' do
let(:push_rule) { create(:push_rule, commit_committer_check: true) }
let(:group) { create(:group, push_rule: push_rule) }
let(:project) { create(:project, namespace_id: group.id) }
context 'when the user is a maintainer' do
let(:current_user) { maintainer }
it { is_expected.not_to be_allowed(:change_commit_committer_check) }
it { is_expected.to be_allowed(:read_commit_committer_check) }
end
context 'when the user is a developer' do
let(:current_user) { developer }
it { is_expected.not_to be_allowed(:change_commit_committer_check) }
it { is_expected.to be_allowed(:read_commit_committer_check) }
end
end
end end
context 'reject_unsigned_commits is not enabled by the current license' do context 'reject_unsigned_commits is not enabled by the current license' do
...@@ -1144,14 +1184,33 @@ describe ProjectPolicy do ...@@ -1144,14 +1184,33 @@ describe ProjectPolicy do
stub_licensed_features(reject_unsigned_commits: true) stub_licensed_features(reject_unsigned_commits: true)
end end
context 'the user is a maintainer' do context 'when the user is a maintainer' do
let(:current_user) { maintainer } let(:current_user) { maintainer }
it { is_expected.to be_allowed(:change_reject_unsigned_commits) } it { is_expected.to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) } it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end end
context 'the user is a developer' do context 'when the user is a developer' do
let(:current_user) { developer }
it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end
context 'it is enabled on global level' do
before do
create(:push_rule_sample, reject_unsigned_commits: true)
end
context 'when the user is a maintainer' do
let(:current_user) { maintainer }
it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end
context 'when the user is a developer' do
let(:current_user) { developer } let(:current_user) { developer }
it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) } it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
...@@ -1159,6 +1218,27 @@ describe ProjectPolicy do ...@@ -1159,6 +1218,27 @@ describe ProjectPolicy do
end end
end end
context 'it is enabled on group level' do
let(:push_rule) { create(:push_rule_without_project, reject_unsigned_commits: true) }
let(:group) { create(:group, push_rule: push_rule) }
let(:project) { create(:project, namespace_id: group.id) }
context 'when the user is a maintainer' do
let(:current_user) { maintainer }
it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end
context 'when the user is a developer' do
let(:current_user) { developer }
it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
it { is_expected.to be_allowed(:read_reject_unsigned_commits) }
end
end
end
context 'when timelogs report feature is enabled' do context 'when timelogs report feature is enabled' do
before do before do
stub_licensed_features(group_timelogs: true) stub_licensed_features(group_timelogs: true)
......
...@@ -85,6 +85,61 @@ describe Groups::CreateService, '#execute' do ...@@ -85,6 +85,61 @@ describe Groups::CreateService, '#execute' do
end end
end end
context 'creating group push rule' do
context 'when feature is available' do
before do
stub_licensed_features(push_rules: true)
end
context 'when there are push rules settings' do
let!(:sample) { create(:push_rule_sample) }
it 'uses the configured push rules settings' do
group = create_group(user, group_params)
expect(group.reload.push_rule).to have_attributes(
force_push_regex: sample.force_push_regex,
deny_delete_tag: sample.deny_delete_tag,
delete_branch_regex: sample.delete_branch_regex,
commit_message_regex: sample.commit_message_regex
)
end
context 'when feature flag is switched off' do
before do
stub_feature_flags(group_push_rules: false)
end
it 'does not create push rule' do
group = create_group(user, group_params)
expect(group.push_rule).to be_nil
end
end
end
context 'when there are not push rules settings' do
it 'is not creating the group push rule' do
group = create_group(user, group_params)
expect(group.push_rule).to be_nil
end
end
end
context 'when feature not is available' do
before do
stub_licensed_features(push_rules: false)
end
it 'ignores the group push rule' do
group = create_group(user, group_params)
expect(group.push_rule).to be_nil
end
end
end
def create_group(user, opts) def create_group(user, opts)
described_class.new(user, opts).execute described_class.new(user, opts).execute
end end
......
...@@ -186,9 +186,14 @@ describe Projects::CreateService, '#execute' do ...@@ -186,9 +186,14 @@ describe Projects::CreateService, '#execute' do
end end
end end
context 'push rules sample' do context 'push rules' do
context 'with sample' do
let!(:sample) { create(:push_rule_sample) } let!(:sample) { create(:push_rule_sample) }
before do
stub_licensed_features(push_rules: true)
end
subject(:push_rule) { create_project(user, opts).push_rule } subject(:push_rule) { create_project(user, opts).push_rule }
it 'creates push rule from sample' do it 'creates push rule from sample' do
...@@ -211,12 +216,94 @@ describe Projects::CreateService, '#execute' do ...@@ -211,12 +216,94 @@ describe Projects::CreateService, '#execute' do
stub_licensed_features(push_rules: false) stub_licensed_features(push_rules: false)
end end
subject(:push_rule) { create_project(user, opts).push_rule }
it 'ignores the push rule sample' do it 'ignores the push rule sample' do
is_expected.to be_nil is_expected.to be_nil
end end
end end
end end
context 'group push rules' do
before do
stub_licensed_features(push_rules: true)
end
context 'project created within a group' do
let(:group) { create(:group) }
let(:opts) do
{
name: "GitLab",
namespace_id: group.id
}
end
before do
group.add_owner(user)
end
context 'when group has push rule defined' do
let(:push_rule) { create(:push_rule_without_project, force_push_regex: 'testing me') }
before do
group.update!(push_rule: push_rule)
group.add_owner(user)
end
it 'creates push rule from group push rule' do
project = create_project(user, opts)
project_push_rule = project.push_rule
expect(project_push_rule).to have_attributes(
force_push_regex: push_rule.force_push_regex,
deny_delete_tag: push_rule.deny_delete_tag,
delete_branch_regex: push_rule.delete_branch_regex,
commit_message_regex: push_rule.commit_message_regex,
is_sample: false
)
expect(project.project_setting.push_rule_id).to eq(project_push_rule.id)
end
context 'when feature flag is switched off' do
let!(:sample) { create(:push_rule_sample) }
before do
stub_feature_flags(group_push_rules: false)
end
it 'creates push rule from sample' do
expect(create_project(user, opts).push_rule).to have_attributes(
force_push_regex: sample.force_push_regex,
deny_delete_tag: sample.deny_delete_tag,
delete_branch_regex: sample.delete_branch_regex,
commit_message_regex: sample.commit_message_regex
)
end
end
end
context 'when group has not push rule defined' do
let!(:sample) { create(:push_rule_sample) }
it 'creates push rule from sample' do
expect(create_project(user, opts).push_rule).to have_attributes(
force_push_regex: sample.force_push_regex,
deny_delete_tag: sample.deny_delete_tag,
delete_branch_regex: sample.delete_branch_regex,
commit_message_regex: sample.commit_message_regex
)
end
end
end
end
context 'when there are no push rules' do
it 'does not create push rule' do
expect(create_project(user, opts).push_rule).to be_nil
end
end
end
context 'when running on a primary node' do context 'when running on a primary node' do
let_it_be(:primary) { create(:geo_node, :primary) } let_it_be(:primary) { create(:geo_node, :primary) }
let_it_be(:secondary) { create(:geo_node) } let_it_be(:secondary) { create(:geo_node) }
......
...@@ -15697,6 +15697,9 @@ msgstr "" ...@@ -15697,6 +15697,9 @@ msgstr ""
msgid "Point to any links you like: documentation, built binaries, or other related materials. These can be internal or external links from your GitLab instance. Duplicate URLs are not allowed." msgid "Point to any links you like: documentation, built binaries, or other related materials. These can be internal or external links from your GitLab instance. Duplicate URLs are not allowed."
msgstr "" msgstr ""
msgid "Pre-defined push rules."
msgstr ""
msgid "Preferences" msgid "Preferences"
msgstr "" msgstr ""
...@@ -18271,6 +18274,9 @@ msgstr "" ...@@ -18271,6 +18274,9 @@ msgstr ""
msgid "Rook" msgid "Rook"
msgstr "" msgstr ""
msgid "Rules that define what git pushes are accepted for a project in this group. All newly created projects in this group will use these settings."
msgstr ""
msgid "Rules that define what git pushes are accepted for a project. All newly created projects will use these settings." msgid "Rules that define what git pushes are accepted for a project. All newly created projects will use these settings."
msgstr "" msgstr ""
......
...@@ -10,7 +10,42 @@ describe 'Group navbar' do ...@@ -10,7 +10,42 @@ describe 'Group navbar' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:structure) do
[
{
nav_item: _('Group overview'),
nav_sub_items: [
_('Details'),
_('Activity')
]
},
{
nav_item: _('Issues'),
nav_sub_items: [
_('List'),
_('Board'),
_('Labels'),
_('Milestones')
]
},
{
nav_item: _('Merge Requests'),
nav_sub_items: []
},
{
nav_item: _('Kubernetes'),
nav_sub_items: []
},
(analytics_nav_item if Gitlab.ee?),
{
nav_item: _('Members'),
nav_sub_items: []
}
]
end
before do before do
stub_feature_flags(group_push_rules: false)
group.add_maintainer(user) group.add_maintainer(user)
sign_in(user) sign_in(user)
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