Commit ab690af8 authored by Sanad Liaquat's avatar Sanad Liaquat

Merge branch 'qa/tn/new_test/push_rules' into 'master'

Push Rules test implementation

Closes gitlab-org/quality/testcases#89, gitlab-org/quality/testcases#90, gitlab-org/quality/testcases#91, gitlab-org/quality/testcases#92, gitlab-org/quality/testcases#93, gitlab-org/quality/testcases#94, gitlab-org/quality/testcases#95, gitlab-org/quality/testcases#96, gitlab-org/quality/testcases#97, and gitlab-org/quality/testcases#98

See merge request gitlab-org/gitlab!16454
parents b3512685 cb71eba4
- 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,11 +69,18 @@ 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 @tag_name
@output += repository.delete_tag(@tag_name)
else
if @directory
@directory.each_child do |f|
@output += repository.add_file(f.basename, f.read) if f.file?
......@@ -84,12 +93,19 @@ module QA
@output += repository.add_file(file_name, file_content)
end
@output += repository.commit(commit_message)
@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