Commit cb71eba4 authored by Tomislav Nikić's avatar Tomislav Nikić 🐙 Committed by Sanad Liaquat

Created a push rules test suite

A push rules test suite was created and to work, an additional GPG
creation class was implemented as well to import the added key and
use it during the test.
parent b3512685
- return unless @project.feature_available?(:push_rules)
- expanded = expanded_by_default?
%section.settings.no-animate#js-push-rules{ class: ('expanded' if expanded) }
%section.settings.no-animate#js-push-rules{ class: ('expanded' if expanded), data: { qa_selector: 'push_rules_content' } }
.settings-header
%h4
Push Rules
......
......@@ -4,7 +4,7 @@
- push_rule = local_assigns.fetch(:push_rule)
.form-check
= form.check_box :commit_committer_check, class: "form-check-input", disabled: !can_change_push_rule?(form.object, :commit_committer_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.label :commit_committer_check, class: "label-bold form-check-label" do
= s_("PushRule|Committer restriction")
%p.text-muted
......
......@@ -3,7 +3,7 @@
= render 'shared/push_rules/reject_unsigned_commits_setting', form: f, push_rule: f.object
.form-check
= f.check_box :deny_delete_tag, class: "form-check-input"
= f.check_box :deny_delete_tag, class: "form-check-input", data: { qa_selector: 'deny_delete_tag_checkbox' }
= f.label :deny_delete_tag, class: "label-bold form-check-label" do
Do not allow users to remove git tags with
%code git push
......@@ -11,14 +11,14 @@
Tags can still be deleted through the web UI.
.form-check
= f.check_box :member_check, class: "form-check-input"
= f.check_box :member_check, class: "form-check-input", data: { qa_selector: 'restrict_author_checkbox' }
= f.label :member_check, "Check whether author is a GitLab user", class: "label-bold form-check-label"
%p.text-muted
Restrict commits by author (email) to existing GitLab users
.form-check
= f.check_box :prevent_secrets, class: "form-check-input"
= f.check_box :prevent_secrets, class: "form-check-input", data: { qa_selector: 'prevent_secrets_checkbox' }
= f.label :prevent_secrets, "Prevent committing secrets to Git", class: "label-bold form-check-label"
%p.text-muted
GitLab will reject any files that are likely to contain secrets.
......@@ -26,7 +26,7 @@
.form-group
= f.label :commit_message_regex, "Commit message", class: "label-bold"
= f.text_field :commit_message_regex, class: "form-control", placeholder: 'Example: Fixes \d+\..*'
= f.text_field :commit_message_regex, class: "form-control", placeholder: 'Example: Fixes \d+\..*', data: { qa_selector: 'commit_message_field' }
.form-text.text-muted
All commit messages must match this
= link_to 'regular expression', 'https://github.com/google/re2/wiki/Syntax'
......@@ -36,7 +36,7 @@
.form-group
= f.label :commit_message_negative_regex, "Commit message negative match", class: 'label-bold'
= f.text_field :commit_message_negative_regex, class: "form-control", placeholder: 'Example: ssh\:\/\/'
= f.text_field :commit_message_negative_regex, class: "form-control", placeholder: 'Example: ssh\:\/\/', data: { qa_selector: 'deny_commit_message_field' }
.form-text.text-muted
No commit message is allowed to match this
= link_to 'regular expression', 'https://github.com/google/re2/wiki/Syntax'
......@@ -46,7 +46,7 @@
.form-group
= f.label :branch_name_regex, "Branch name", class: "label-bold"
= f.text_field :branch_name_regex, class: "form-control", placeholder: 'Example: (feature|hotfix)\/*'
= f.text_field :branch_name_regex, class: "form-control", placeholder: 'Example: (feature|hotfix)\/*', data: { qa_selector: 'branch_name_field' }
.form-text.text-muted
All branch names must match this
= link_to 'regular expression', 'https://github.com/google/re2/wiki/Syntax'
......@@ -55,7 +55,7 @@
.form-group
= f.label :author_email_regex, "Commit author's email", class: "label-bold"
= f.text_field :author_email_regex, class: "form-control", placeholder: 'Example: @my-company.com$'
= f.text_field :author_email_regex, class: "form-control", placeholder: 'Example: @my-company.com$', data: { qa_selector: 'author_email_field' }
.form-text.text-muted
All commit author's email must match this
= link_to 'regular expression', 'https://github.com/google/re2/wiki/Syntax'
......@@ -64,7 +64,7 @@
.form-group
= f.label :file_name_regex, "Prohibited file names", class: "label-bold"
= f.text_field :file_name_regex, class: "form-control", placeholder: 'Example: (jar|exe)$'
= f.text_field :file_name_regex, class: "form-control", placeholder: 'Example: (jar|exe)$', data: { qa_selector: 'file_name_field' }
.form-text.text-muted
All commited filenames must not match this
= link_to 'regular expression', 'https://github.com/google/re2/wiki/Syntax'
......@@ -73,9 +73,9 @@
.form-group
= f.label :max_file_size, "Maximum file size (MB)", class: "label-bold"
= f.number_field :max_file_size, class: "form-control", min: 0
= f.number_field :max_file_size, class: "form-control", min: 0, data: { qa_selector: 'file_size_field' }
.form-text.text-muted
Pushes that contain added or updated files that exceed this file size are rejected.
Set to 0 to allow files of any size.
= f.submit "Save Push Rules", class: "btn btn-success"
= f.submit "Save Push Rules", class: "btn btn-success", data: { qa_selector: 'submit_settings_button' }
......@@ -4,7 +4,7 @@
- push_rule = local_assigns.fetch(:push_rule)
.form-check
= form.check_box :reject_unsigned_commits, class: "form-check-input", disabled: !can_change_push_rule?(form.object, :reject_unsigned_commits)
= 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.label :reject_unsigned_commits, class: "label-bold form-check-label" do
Reject unsigned commits
%p.text-muted
......
......@@ -23,6 +23,7 @@ module QA
autoload :Feature, 'qa/runtime/feature'
autoload :Fixtures, 'qa/runtime/fixtures'
autoload :Logger, 'qa/runtime/logger'
autoload :GPG, 'qa/runtime/gpg'
module API
autoload :Client, 'qa/runtime/api/client'
......@@ -67,7 +68,9 @@ module QA
autoload :Fork, 'qa/resource/fork'
autoload :SSHKey, 'qa/resource/ssh_key'
autoload :Snippet, 'qa/resource/snippet'
autoload :Tag, 'qa/resource/tag'
autoload :ProjectMember, 'qa/resource/project_member'
autoload :UserGPG, 'qa/resource/user_gpg'
module Events
autoload :Base, 'qa/resource/events/base'
......
......@@ -109,6 +109,8 @@ module QA
autoload :MirroringRepositories, 'qa/ee/page/project/settings/mirroring_repositories'
autoload :Main, 'qa/ee/page/project/settings/main'
autoload :MergeRequestApproval, 'qa/ee/page/project/settings/merge_request_approval'
autoload :Repository, 'qa/ee/page/project/settings/repository'
autoload :PushRules, 'qa/ee/page/project/settings/push_rules'
end
module Operations
......
# frozen_string_literal: true
module QA
module EE
module Page
module Project
module Settings
class PushRules < QA::Page::Base
view 'ee/app/views/shared/push_rules/_form.html.haml' do
element :deny_delete_tag_checkbox
element :restrict_author_checkbox
element :prevent_secrets_checkbox
element :commit_message_field
element :deny_commit_message_field
element :branch_name_field
element :author_email_field
element :file_name_field
element :file_size_field
element :submit_settings_button
end
view 'ee/app/views/shared/push_rules/_reject_unsigned_commits_setting.html.haml' do
element :reject_unsigned_commits_checkbox
end
view 'ee/app/views/shared/push_rules/_commit_committer_check_setting.html.haml' do
element :committer_restriction_checkbox
end
def check_reject_unsigned_commits
check_element :reject_unsigned_commits_checkbox
end
def check_committer_restriction
check_element :committer_restriction_checkbox
end
def check_deny_delete_tag
check_element :deny_delete_tag_checkbox
end
def check_restrict_author
check_element :restrict_author_checkbox
end
def check_prevent_secrets
check_element :prevent_secrets_checkbox
end
def fill_commit_message_rule(message)
fill_element :commit_message_field, message
end
def fill_deny_commit_message_rule(message)
fill_element :deny_commit_message_field, message
end
def fill_branch_name(name)
fill_element :branch_name_field, name
end
def fill_author_email(email)
fill_element :author_email_field, email
end
def fill_file_name(file_name)
fill_element :file_name_field, file_name
end
def fill_file_size(file_size)
fill_element :file_size_field, file_size
end
def click_submit
click_element :submit_settings_button
end
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module EE
module Page
module Project
module Settings
module Repository
def self.prepended(page)
page.module_eval do
view 'ee/app/views/projects/push_rules/_index.html.haml' do
element :push_rules_content
end
end
end
def expand_push_rules(&block)
expand_section(:push_rules_content) do
PushRules.perform(&block)
end
end
end
end
end
end
end
end
......@@ -14,7 +14,7 @@ module QA
include Scenario::Actable
RepositoryCommandError = Class.new(StandardError)
attr_writer :use_lfs
attr_writer :use_lfs, :gpg_key_id
attr_accessor :env_vars
InvalidCredentialsError = Class.new(RuntimeError)
......@@ -25,6 +25,7 @@ module QA
# .netrc can be utilised
self.env_vars = [%Q{HOME="#{tmp_home_dir}"}]
@use_lfs = false
@gpg_key_id = nil
end
def self.perform(*args)
......@@ -99,10 +100,18 @@ module QA
git_lfs_track_result.to_s + git_add_result.to_s
end
def delete_tag(tag_name)
run(%Q{git push origin --delete #{tag_name}}).to_s
end
def commit(message)
run(%Q{git commit -m "#{message}"}).to_s
end
def commit_with_gpg(message)
run(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(which gpg) && git commit -S -m "#{message}"}).to_s
end
def push_changes(branch = 'master')
run("git push #{uri} #{branch}").to_s
end
......
......@@ -47,3 +47,5 @@ module QA
end
end
end
QA::Page::Project::Settings::Repository.prepend_if_ee('QA::EE::Page::Project::Settings::Repository')
......@@ -8,9 +8,9 @@ module QA
class Push < Base
attr_accessor :file_name, :file_content, :commit_message,
:branch_name, :new_branch, :output, :repository_http_uri,
:repository_ssh_uri, :ssh_key, :user, :use_lfs
:repository_ssh_uri, :ssh_key, :user, :use_lfs, :tag_name
attr_writer :remote_branch
attr_writer :remote_branch, :gpg_key_id
def initialize
@file_name = 'file.txt'
......@@ -21,6 +21,8 @@ module QA
@repository_http_uri = ""
@ssh_key = nil
@use_lfs = false
@tag_name = nil
@gpg_key_id = nil
end
def remote_branch
......@@ -67,29 +69,43 @@ module QA
email = user.email
end
unless @gpg_key_id.nil?
repository.gpg_key_id = @gpg_key_id
end
@output += repository.clone
repository.configure_identity(username, email)
@output += repository.checkout(branch_name, new_branch: new_branch)
if @directory
@directory.each_child do |f|
@output += repository.add_file(f.basename, f.read) if f.file?
end
elsif @files
@files.each do |f|
repository.add_file(f[:name], f[:content])
end
if @tag_name
@output += repository.delete_tag(@tag_name)
else
@output += repository.add_file(file_name, file_content)
end
if @directory
@directory.each_child do |f|
@output += repository.add_file(f.basename, f.read) if f.file?
end
elsif @files
@files.each do |f|
repository.add_file(f[:name], f[:content])
end
else
@output += repository.add_file(file_name, file_content)
end
@output += repository.commit(commit_message)
@output += repository.push_changes("#{branch_name}:#{remote_branch}")
@output += commit_to repository
@output += repository.push_changes("#{branch_name}:#{remote_branch}")
end
repository.delete_ssh_key
end
end
private
def commit_to(repository)
@gpg_key_id.nil? ? repository.commit(@commit_message) : repository.commit_with_gpg(@commit_message)
end
end
end
end
......
# frozen_string_literal: true
module QA
module Resource
class Tag < Base
attr_accessor :project, :name, :ref
def resource_web_url(resource)
super
rescue ResourceURLMissingError
# this particular resource does not expose a web_url property
end
def api_get_path
"/projects/#{project.id}/repository/tags/#{name}"
end
def api_post_path
"/projects/#{project.id}/repository/tags"
end
def api_post_body
{
tag_name: name,
ref: ref
}
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class UserGPG < Base
attr_accessor :id, :gpg
attr_reader :key_id
def initialize
@gpg = Runtime::GPG.new
@key_id = @gpg.key_id
end
def fabricate_via_api!
super
@id = self.api_response[:id]
rescue ResourceFabricationFailedError => error
if error.message.include? 'has already been taken'
self
else
raise ResourceFabricationFailedError error
end
end
def resource_web_url(resource)
super
rescue ResourceURLMissingError
# this particular resource does not expose a web_url property
end
def api_get_path
"/user/gpg_keys/#{@id}"
end
def api_post_path
'/user/gpg_keys'
end
def api_post_body
{
key: @gpg.key
}
end
end
end
end
# frozen_string_literal: true
module QA
module Runtime
class GPG
attr_reader :key, :key_id
def initialize
@key_id = 'B8358D73048DACC4'
import_key(File.expand_path('qa/ee/fixtures/gpg/admin.asc'))
@key = collect_key.first
end
private
def import_key(path)
import_key = "gpg --import #{path}"
execute(import_key)
end
def collect_key
get_ascii_format = "gpg --armor --export #{@key_id}"
execute(get_ascii_format)
end
def execute(command)
Open3.capture2e(*command) do |stdin, out, wait|
out.each_char { |char| print char }
if wait.value.exited? && wait.value.exitstatus.nonzero?
raise CommandError, "Command `#{command}` failed!"
end
end
end
end
end
end
......@@ -25,6 +25,10 @@ module QA
Runtime::Env.user_password || default_password
end
def email
default_email
end
def ldap_user?
Runtime::Env.ldap_username && Runtime::Env.ldap_password
end
......
# frozen_string_literal: true
module QA
context 'Create' do
context 'Push Rules' do
describe 'using non signed commits' do
file_name_limitation = 'denied_file'
file_size_limitation = 1
authors_email_limitation = '(admin@example.com|root@gitlab.com)'
branch_name_limitation = 'master'
needed_phrase_limitation = 'allowed commit'
deny_message_phrase_limitation = 'denied commit'
before :context do
prepare
Page::Project::Settings::Repository.perform do |repository|
repository.expand_push_rules do |push_rules|
push_rules.fill_file_name file_name_limitation
push_rules.fill_file_size file_size_limitation
push_rules.fill_author_email authors_email_limitation
push_rules.fill_branch_name branch_name_limitation
push_rules.fill_commit_message_rule needed_phrase_limitation
push_rules.fill_deny_commit_message_rule deny_message_phrase_limitation
push_rules.check_prevent_secrets
push_rules.check_restrict_author
push_rules.check_deny_delete_tag
push_rules.click_submit
end
end
end
it 'restricts files by name and size' do
large_file = [{
name: 'file',
content: SecureRandom.hex(1000000)
}]
wrongly_named_file = [{
name: file_name_limitation,
content: SecureRandom.hex(100)
}]
expect_no_error_on_push file: standard_file
expect_error_on_push file: large_file
expect_error_on_push file: wrongly_named_file
end
it 'restricts users by email format' do
gitlab_user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
expect_no_error_on_push file: standard_file
expect_error_on_push file: standard_file, user: gitlab_user
end
it 'restricts branches by branch name' do
expect_no_error_on_push file: standard_file
expect_error_on_push file: standard_file, branch: 'forbidden_branch'
end
it 'restricts commit by message format' do
expect_no_error_on_push file: standard_file, commit_message: needed_phrase_limitation
expect_error_on_push file: standard_file, commit_message: 'forbidden message'
expect_error_on_push file: standard_file, commit_message: "#{needed_phrase_limitation} - #{deny_message_phrase_limitation}"
end
it 'restricts committing files with secrets' do
secret_file = [{
name: 'id_rsa',
content: SecureRandom.hex(100)
}]
expect_no_error_on_push file: standard_file
expect_error_on_push file: secret_file
end
it 'restricts commits by user' do
expect_no_error_on_push file: standard_file
expect_error_on_push file: standard_file, user: root_user
end
it 'restricts removal of tag' do
tag = Resource::Tag.fabricate_via_api! do |tag|
tag.project = @project
tag.ref = 'master'
tag.name = 'test_tag'
end
expect_no_error_on_push file: standard_file
expect_error_on_push file: standard_file, tag: tag.name
end
end
describe 'using signed commits' do
before :context do
prepare
Page::Project::Settings::Repository.perform do |repository|
repository.expand_push_rules do |push_rules|
push_rules.check_reject_unsigned_commits
push_rules.check_committer_restriction
push_rules.click_submit
end
end
@gpg = Resource::UserGPG.fabricate_via_api!
end
it 'restricts to signed commits' do
expect_no_error_on_push file: standard_file, gpg: @gpg
expect_error_on_push file: standard_file
end
it 'restricts commits to current authenticated user' do
gitlab_user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
expect_no_error_on_push file: standard_file, gpg: @gpg
expect_error_on_push file: standard_file, gpg: @gpg, user: gitlab_user
end
end
def standard_file
[{
name: 'file',
content: SecureRandom.hex(100)
}]
end
def root_user
Resource::User.new.tap do |user|
user.username = 'root'
user.name = 'GitLab QA'
user.email = 'root@gitlab.com'
user.password = nil
end
end
def push(commit_message:, branch:, file:, user:, tag:, gpg:)
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = @project
push.commit_message = commit_message
push.new_branch = branch != 'master'
push.branch_name = branch
push.user = user
push.files = file if tag.nil?
push.tag_name = tag unless tag.nil?
push.gpg_key_id = gpg.key_id unless gpg.nil?
end
end
def expect_no_error_on_push(commit_message: 'allowed commit', branch: 'master', file:, user: Runtime::User, tag: nil, gpg: nil)
expect do
push commit_message: commit_message, branch: branch, file: file, user: user, tag: tag, gpg: gpg
end.not_to raise_error
end
def expect_error_on_push(commit_message: 'allowed commit', branch: 'master', file:, user: Runtime::User, tag: nil, gpg: nil)
expect do
push commit_message: commit_message, branch: branch, file: file, user: user, tag: tag, gpg: gpg
end.to raise_error(QA::Git::Repository::RepositoryCommandError)
end
def prepare
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
@project = Resource::Project.fabricate_via_api! do |project|
project.name = 'push_rules'
end
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = @project
push.files = standard_file
end
@project.visit!
Page::Project::Menu.perform(&:go_to_repository_settings)
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