Commit a20d4391 authored by Sanad Liaquat's avatar Sanad Liaquat Committed by Mark Lapierre

Add e2e test for 2FA recovery via SSH

Also moves ssh and command related code out of qa/git/repository.rb
into its own files and add tests for them
parent c70d2901
......@@ -20,7 +20,7 @@
= link_to _('Manage two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-info'
- else
.gl-mb-3
= link_to _('Enable two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-success'
= link_to _('Enable two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-success', data: { qa_selector: 'enable_2fa_button' }
%hr
- if display_providers_on_profile?
......
......@@ -593,10 +593,12 @@ module QA
autoload :Api, 'qa/support/api'
autoload :Dates, 'qa/support/dates'
autoload :Repeater, 'qa/support/repeater'
autoload :Run, 'qa/support/run'
autoload :Retrier, 'qa/support/retrier'
autoload :Waiter, 'qa/support/waiter'
autoload :WaitForRequests, 'qa/support/wait_for_requests'
autoload :OTP, 'qa/support/otp'
autoload :SSH, 'qa/support/ssh'
end
end
......
......@@ -2,10 +2,8 @@
require 'cgi'
require 'uri'
require 'open3'
require 'fileutils'
require 'tmpdir'
require 'tempfile'
require 'securerandom'
module QA
......@@ -13,8 +11,7 @@ module QA
class Repository
include Scenario::Actable
include Support::Repeater
RepositoryCommandError = Class.new(StandardError)
include Support::Run
attr_writer :use_lfs, :gpg_key_id
attr_accessor :env_vars
......@@ -64,7 +61,7 @@ module QA
end
def clone(opts = '')
clone_result = run("git clone #{opts} #{uri} ./", max_attempts: 3)
clone_result = run_git("git clone #{opts} #{uri} ./", max_attempts: 3)
return clone_result.response unless clone_result.success?
enable_lfs_result = enable_lfs if use_lfs?
......@@ -74,7 +71,7 @@ module QA
def checkout(branch_name, new_branch: false)
opts = new_branch ? '-b' : ''
run(%Q{git checkout #{opts} "#{branch_name}"}).to_s
run_git(%Q{git checkout #{opts} "#{branch_name}"}).to_s
end
def shallow_clone
......@@ -82,8 +79,8 @@ module QA
end
def configure_identity(name, email)
run(%Q{git config user.name "#{name}"})
run(%Q{git config user.email #{email}})
run_git(%Q{git config user.name "#{name}"})
run_git(%Q{git config user.email #{email}})
end
def commit_file(name, contents, message)
......@@ -97,33 +94,33 @@ module QA
::File.write(name, contents)
if use_lfs?
git_lfs_track_result = run(%Q{git lfs track #{name} --lockable})
git_lfs_track_result = run_git(%Q{git lfs track #{name} --lockable})
return git_lfs_track_result.response unless git_lfs_track_result.success?
end
git_add_result = run(%Q{git add #{name}})
git_add_result = run_git(%Q{git add #{name}})
git_lfs_track_result.to_s + git_add_result.to_s
end
def add_tag(tag_name)
run("git tag #{tag_name}").to_s
run_git("git tag #{tag_name}").to_s
end
def delete_tag(tag_name)
run(%Q{git push origin --delete #{tag_name}}, max_attempts: 3).to_s
run_git(%Q{git push origin --delete #{tag_name}}, max_attempts: 3).to_s
end
def commit(message)
run(%Q{git commit -m "#{message}"}, max_attempts: 3).to_s
run_git(%Q{git commit -m "#{message}"}, max_attempts: 3).to_s
end
def commit_with_gpg(message)
run(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(command -v gpg) && git commit -S -m "#{message}"}).to_s
run_git(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(command -v gpg) && git commit -S -m "#{message}"}).to_s
end
def current_branch
run("git rev-parse --abbrev-ref HEAD").to_s
run_git("git rev-parse --abbrev-ref HEAD").to_s
end
def push_changes(branch = 'master', push_options: nil)
......@@ -131,53 +128,48 @@ module QA
cmd << push_options_hash_to_string(push_options)
cmd << uri
cmd << branch
run(cmd.compact.join(' '), max_attempts: 3).to_s
run_git(cmd.compact.join(' '), max_attempts: 3).to_s
end
def push_all_branches
run("git push --all").to_s
run_git("git push --all").to_s
end
def push_tags_and_branches(branches)
run("git push --tags origin #{branches.join(' ')}").to_s
run_git("git push --tags origin #{branches.join(' ')}").to_s
end
def merge(branch)
run("git merge #{branch}")
run_git("git merge #{branch}")
end
def init_repository
run("git init")
run_git("git init")
end
def pull(repository = nil, branch = nil)
run(['git', 'pull', repository, branch].compact.join(' '))
run_git(['git', 'pull', repository, branch].compact.join(' '))
end
def commits
run('git log --oneline').to_s.split("\n")
run_git('git log --oneline').to_s.split("\n")
end
def use_ssh_key(key)
@private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}")
File.binwrite(private_key_file, key.private_key)
File.chmod(0700, private_key_file)
@known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}")
keyscan_params = ['-H']
keyscan_params << "-p #{uri.port}" if uri.port
keyscan_params << uri.host
res = run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
return res.response unless res.success?
@ssh = Support::SSH.perform do |ssh|
ssh.key = key
ssh.uri = uri
ssh.setup(env: self.env_vars)
ssh
end
self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"}
self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{ssh.private_key_file.path} -o UserKnownHostsFile=#{ssh.known_hosts_file.path}"}
end
def delete_ssh_key
return unless ssh_key_set?
private_key_file.close(true)
known_hosts_file.close(true)
ssh.delete
end
def push_with_git_protocol(version, file_name, file_content, commit_message = 'Initial commit')
......@@ -192,13 +184,13 @@ module QA
def git_protocol=(value)
raise ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2" unless %w[0 1 2].include?(value.to_s)
run("git config protocol.version #{value}")
run_git("git config protocol.version #{value}")
end
def fetch_supported_git_protocol
# ls-remote is one command known to respond to Git protocol v2 so we use
# it to get output including the version reported via Git tracing
result = run("git ls-remote #{uri}", env: "GIT_TRACE_PACKET=1", max_attempts: 3)
result = run_git("git ls-remote #{uri}", max_attempts: 3, env: [*self.env_vars, "GIT_TRACE_PACKET=1"])
result.response[/git< version (\d+)/, 1] || 'unknown'
end
......@@ -219,19 +211,10 @@ module QA
private
attr_reader :uri, :username, :password, :known_hosts_file,
:private_key_file, :use_lfs
attr_reader :uri, :username, :password, :ssh, :use_lfs
alias_method :use_lfs?, :use_lfs
Result = Struct.new(:command, :exitstatus, :response) do
alias_method :to_s, :response
def success?
exitstatus == 0 && !response.include?('Error encountered')
end
end
def add_credentials?
return false if !username || !password
return true unless ssh_key_set?
......@@ -240,7 +223,7 @@ module QA
end
def ssh_key_set?
!private_key_file.nil?
ssh && !ssh.private_key_file.nil?
end
def enable_lfs
......@@ -249,33 +232,11 @@ module QA
touch_gitconfig_result = run("touch #{tmp_home_dir}/.gitconfig")
return touch_gitconfig_result.response unless touch_gitconfig_result.success?
git_lfs_install_result = run('git lfs install')
git_lfs_install_result = run_git('git lfs install')
touch_gitconfig_result.to_s + git_lfs_install_result.to_s
end
def run(command_str, env: [], max_attempts: 1)
command = [env_vars, *env, command_str, '2>&1'].compact.join(' ')
result = nil
repeat_until(max_attempts: max_attempts, raise_on_failure: false) do
Runtime::Logger.debug "Git: pwd=[#{Dir.pwd}], command=[#{command}]"
output, status = Open3.capture2e(command)
output.chomp!
Runtime::Logger.debug "Git: output=[#{output}], exitstatus=[#{status.exitstatus}]"
result = Result.new(command, status.exitstatus, output)
result.success?
end
unless result.success?
raise RepositoryCommandError, "The command #{result.command} failed (#{result.exitstatus}) with the following output:\n#{result.response}"
end
result
end
def default_credentials
if ::QA::Runtime::User.ldap_user?
[Runtime::User.ldap_username, Runtime::User.ldap_password]
......@@ -333,6 +294,10 @@ module QA
def netrc_already_contains_content?
read_netrc_content.grep(/^#{Regexp.escape(netrc_content)}$/).any?
end
def run_git(command_str, env: self.env_vars, max_attempts: 1)
run(command_str, env: env, max_attempts: max_attempts, log_prefix: 'Git: ')
end
end
end
end
......@@ -7,6 +7,7 @@ module QA
class Show < Page::Base
view 'app/views/profiles/accounts/show.html.haml' do
element :delete_account_button, required: true
element :enable_2fa_button
end
view 'app/assets/javascripts/profile/account/components/delete_account_modal.vue' do
......@@ -14,6 +15,10 @@ module QA
element :confirm_delete_account_button
end
def click_enable_2fa_button
click_element(:enable_2fa_button)
end
def delete_account(password)
click_element(:delete_account_button)
......
# frozen_string_literal: true
module QA
context 'Manage', :requires_admin, :skip_live_env do
describe '2FA' do
let!(:user) { Resource::User.fabricate_via_api! }
let!(:user_api_client) { Runtime::API::Client.new(:gitlab, user: user) }
let(:address) { QA::Runtime::Scenario.gitlab_address }
let(:uri) { URI.parse(address) }
let(:ssh_port) { uri.port == 80 ? '' : '2222' }
let!(:ssh_key) do
Resource::SSHKey.fabricate_via_api! do |resource|
resource.title = "key for ssh tests #{Time.now.to_f}"
resource.api_client = user_api_client
end
end
before do
enable_2fa_for_user(user)
end
it 'allows 2FA code recovery via ssh' do
recovery_code = Support::SSH.perform do |ssh|
ssh.key = ssh_key
ssh.uri = address.gsub(uri.port.to_s, ssh_port)
ssh.setup
output = ssh.reset_2fa_codes
output.scan(/([A-Za-z0-9]{16})\n/).flatten.first
end
Flow::Login.sign_in(as: user, skip_page_validation: true)
Page::Main::TwoFactorAuth.perform do |two_fa_auth|
two_fa_auth.set_2fa_code(recovery_code)
two_fa_auth.click_verify_code_button
end
expect(Page::Main::Menu.perform(&:signed_in?)).to be_truthy
Page::Main::Menu.perform(&:sign_out)
Flow::Login.sign_in(as: user, skip_page_validation: true)
Page::Main::TwoFactorAuth.perform do |two_fa_auth|
two_fa_auth.set_2fa_code(recovery_code)
two_fa_auth.click_verify_code_button
end
expect(page).to have_text('Invalid two-factor code')
end
def enable_2fa_for_user(user)
Flow::Login.while_signed_in(as: user) do
Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_account)
Page::Profile::Accounts::Show.perform(&:click_enable_2fa_button)
Page::Profile::TwoFactorAuth.perform do |two_fa_auth|
otp = QA::Support::OTP.new(two_fa_auth.otp_secret_content)
two_fa_auth.set_pin_code(otp.fresh_otp)
two_fa_auth.click_register_2fa_app_button
two_fa_auth.click_proceed_button
end
end
end
end
end
end
......@@ -41,7 +41,7 @@ module QA
retry_on_fail do
expect { push_new_file('oversize_file_2.bin', wait_for_push: false) }
.to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/)
.to raise_error(QA::Support::Run::CommandError, /remote: fatal: pack exceeds maximum allowed size/)
end
end
......
......@@ -35,7 +35,7 @@ module QA
roles: Resource::ProtectedBranch::Roles::NO_ONE
})
expect { push_new_file(branch_name) }.to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: GitLab: You are not allowed to push code to protected branches on this project\.([\s\S]+)\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
expect { push_new_file(branch_name) }.to raise_error(QA::Support::Run::CommandError, /remote: GitLab: You are not allowed to push code to protected branches on this project\.([\s\S]+)\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
end
......
......@@ -91,7 +91,7 @@ module QA
repository.init_repository
expect { repository.pull(repository_uri_ssh, branch_name) }
.to raise_error(QA::Git::Repository::RepositoryCommandError, /fatal: Could not read from remote repository\./)
.to raise_error(QA::Support::Run::CommandError, /fatal: Could not read from remote repository\./)
end
end
......
......@@ -90,7 +90,7 @@ module QA
repository.init_repository
expect { repository.pull(repository_uri_ssh, branch_name) }
.to raise_error(QA::Git::Repository::RepositoryCommandError, /fatal: Could not read from remote repository\./)
.to raise_error(QA::Support::Run::CommandError, /fatal: Could not read from remote repository\./)
end
end
......
......@@ -88,7 +88,7 @@ module QA
end
it 'denies access', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/860' do
expect { push_a_project_with_ssh_key(key) }.to raise_error(QA::Git::Repository::RepositoryCommandError, /fatal: Could not read from remote repository/)
expect { push_a_project_with_ssh_key(key) }.to raise_error(QA::Support::Run::CommandError, /fatal: Could not read from remote repository/)
end
end
end
......
......@@ -144,7 +144,7 @@ module QA
def expect_error_on_push(for_file: 'file', as_user:)
expect { push branch: 'master', file: for_file, as_user: as_user }.to raise_error(
QA::Git::Repository::RepositoryCommandError)
QA::Support::Run::CommandError)
end
def expect_no_error_on_push(for_file: 'file', as_user:)
......
......@@ -191,7 +191,7 @@ module QA
def expect_error_on_push(commit_message: 'allowed commit', branch: 'master', file:, user: @creator, tag: nil, gpg: nil, error: 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, /#{error}/)
end.to raise_error(QA::Support::Run::CommandError, /#{error}/)
end
def prepare
......
......@@ -11,7 +11,7 @@ module QA
shared_examples 'only user with access pushes and merges' do
it 'unselected maintainer user fails to push' do
expect { push_new_file(branch_name, as_user: user_maintainer) }.to raise_error(
QA::Git::Repository::RepositoryCommandError,
QA::Support::Run::CommandError,
/remote: GitLab: You are not allowed to push code to protected branches on this project\.([\s\S]+)\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
......
......@@ -13,11 +13,14 @@ module QA
# Fetches a fresh OTP and returns it only after rotp provides the same OTP twice
# An OTP is valid for 30 seconds so 70 attempts with 0.5 interval would ensure we complete 1 cycle
Support::Retrier.retry_until(max_attempts: 70, sleep_interval: 0.5) do
QA::Runtime::Logger.debug("Fetching a fresh OTP...")
Support::Retrier.retry_until(max_attempts: 70, sleep_interval: 0.5, log: false) do
otps << @rotp.now
otps.size >= 3 && otps[-1] == otps[-2] && otps[-1] != otps[-3]
end
QA::Runtime::Logger.debug("Fetched OTP: #{otps.last}")
otps.last
end
end
......
......@@ -34,15 +34,17 @@ module QA
result
end
def retry_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false)
def retry_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false, log: true)
# For backwards-compatibility
max_attempts = 3 if max_attempts.nil? && max_duration.nil?
start_msg ||= ["with retry_until:"]
start_msg << "max_attempts: #{max_attempts};" if max_attempts
start_msg << "max_duration: #{max_duration};" if max_duration
start_msg << "reload_page: #{reload_page}; sleep_interval: #{sleep_interval}; raise_on_failure: #{raise_on_failure}; retry_on_exception: #{retry_on_exception}"
QA::Runtime::Logger.debug(start_msg.join(' '))
if log
start_msg ||= ["with retry_until:"]
start_msg << "max_attempts: #{max_attempts};" if max_attempts
start_msg << "max_duration: #{max_duration};" if max_duration
start_msg << "reload_page: #{reload_page}; sleep_interval: #{sleep_interval}; raise_on_failure: #{raise_on_failure}; retry_on_exception: #{retry_on_exception}"
QA::Runtime::Logger.debug(start_msg.join(' '))
end
result = nil
repeat_until(
......@@ -51,7 +53,8 @@ module QA
reload_page: reload_page,
sleep_interval: sleep_interval,
raise_on_failure: raise_on_failure,
retry_on_exception: retry_on_exception
retry_on_exception: retry_on_exception,
log: log
) do
result = yield
end
......
# frozen_string_literal: true
require 'open3'
module QA
module Support
module Run
include QA::Support::Repeater
CommandError = Class.new(StandardError)
Result = Struct.new(:command, :exitstatus, :response) do
alias_method :to_s, :response
def success?
exitstatus == 0 && !response.include?('Error encountered')
end
end
def run(command_str, env: [], max_attempts: 1, log_prefix: '')
command = [*env, command_str, '2>&1'].compact.join(' ')
result = nil
repeat_until(max_attempts: max_attempts, raise_on_failure: false) do
Runtime::Logger.debug "#{log_prefix}pwd=[#{Dir.pwd}], command=[#{command}]"
output, status = Open3.capture2e(command)
output.chomp!
Runtime::Logger.debug "#{log_prefix}output=[#{output}], exitstatus=[#{status.exitstatus}]"
result = Result.new(command, status.exitstatus, output)
result.success?
end
unless result.success?
raise CommandError, "The command #{result.command} failed (#{result.exitstatus}) with the following output:\n#{result.response}"
end
result
end
end
end
end
# frozen_string_literal: true
require 'tempfile'
require 'etc'
module QA
module Support
class SSH
include Scenario::Actable
include Support::Run
attr_accessor :known_hosts_file, :private_key_file, :key
attr_reader :uri
def initialize
@private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}")
@known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}")
end
def uri=(address)
@uri = URI(address)
end
def setup(env: nil)
File.binwrite(private_key_file, key.private_key)
File.chmod(0700, private_key_file)
keyscan_params = ['-H']
keyscan_params << "-p #{uri_port}" if uri_port
keyscan_params << uri.host
res = run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}", env: env, log_prefix: 'SSH: ')
return res.response unless res.success?
true
end
def delete
private_key_file.close(true)
known_hosts_file.close(true)
end
def reset_2fa_codes
ssh_params = [uri.host]
ssh_params << "-p #{uri_port}" if uri_port
ssh_params << "2fa_recovery_codes"
run("echo yes | ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path} #{git_user}@#{ssh_params.join(' ')}", log_prefix: 'SSH: ').to_s
end
private
def uri_port
uri.port && (uri.port != 80) ? uri.port : nil
end
def git_user
QA::Runtime::Env.running_in_ci? || [443, 80].include?(uri.port) ? 'git' : Etc.getlogin
end
end
end
end
......@@ -6,7 +6,16 @@ RSpec.describe QA::Git::Repository do
shared_context 'unresolvable git directory' do
let(:repo_uri) { 'http://foo/bar.git' }
let(:repo_uri_with_credentials) { 'http://root@foo/bar.git' }
let(:repository) { described_class.new.tap { |r| r.uri = repo_uri } }
let(:env_vars) { [%q{HOME="temp"}] }
let(:extra_env_vars) { [] }
let(:run_params) { { env: env_vars + extra_env_vars, log_prefix: "Git: " } }
let(:repository) do
described_class.new.tap do |r|
r.uri = repo_uri
r.env_vars = env_vars
end
end
let(:tmp_git_dir) { Dir.mktmpdir }
let(:tmp_netrc_dir) { Dir.mktmpdir }
......@@ -28,14 +37,13 @@ RSpec.describe QA::Git::Repository do
end
shared_examples 'command with retries' do
let(:extra_args) { {} }
let(:result_output) { +'Command successful' }
let(:result) { described_class::Result.new(any_args, 0, result_output) }
let(:command_return) { result_output }
context 'when command is successful' do
it 'returns the #run command Result output' do
expect(repository).to receive(:run).with(command, extra_args.merge(max_attempts: 3)).and_return(result)
expect(repository).to receive(:run).with(command, run_params.merge(max_attempts: 3)).and_return(result)
expect(call_method).to eq(command_return)
end
......@@ -52,10 +60,10 @@ RSpec.describe QA::Git::Repository do
end
context 'and retried command is not successful after 3 attempts' do
it 'raises a RepositoryCommandError exception' do
it 'raises a CommandError exception' do
expect(Open3).to receive(:capture2e).and_return([+'FAILURE', double(exitstatus: 42)]).exactly(3).times
expect { call_method }.to raise_error(described_class::RepositoryCommandError, /The command .* failed \(42\) with the following output:\nFAILURE/)
expect { call_method }.to raise_error(QA::Support::Run::CommandError, /The command .* failed \(42\) with the following output:\nFAILURE/)
end
end
end
......@@ -182,7 +190,7 @@ RSpec.describe QA::Git::Repository do
describe '#git_protocol=' do
[0, 1, 2].each do |version|
it "configures git to use protocol version #{version}" do
expect(repository).to receive(:run).with("git config protocol.version #{version}")
expect(repository).to receive(:run).with("git config protocol.version #{version}", run_params.merge(max_attempts: 1))
repository.git_protocol = version
end
......@@ -200,7 +208,7 @@ RSpec.describe QA::Git::Repository do
let(:command) { "git ls-remote #{repo_uri_with_credentials}" }
let(:result_output) { +'packet: git< version 2' }
let(:command_return) { '2' }
let(:extra_args) { { env: "GIT_TRACE_PACKET=1" } }
let(:extra_env_vars) { ["GIT_TRACE_PACKET=1"] }
end
it "reports the detected version" do
......
# frozen_string_literal: true
RSpec.describe QA::Support::Run do
let(:class_instance) { (Class.new { include QA::Support::Run }).new }
let(:response) { 'successful response' }
let(:command) { 'some command' }
let(:expected_result) { described_class::Result.new("#{command} 2>&1", 0, response) }
it 'runs successfully' do
expect(Open3).to receive(:capture2e).and_return([+response, double(exitstatus: 0)])
expect(class_instance.run(command)).to eq(expected_result)
end
it 'retries twice and succeeds the third time' do
allow(Open3).to receive(:capture2e).and_return([+'', double(exitstatus: 1)]).twice
allow(Open3).to receive(:capture2e).and_return([+response, double(exitstatus: 0)])
expect(class_instance.run(command)).to eq(expected_result)
end
it 'raises an exception on 3rd failure' do
allow(Open3).to receive(:capture2e).and_return([+'FAILURE', double(exitstatus: 1)]).thrice
expect { class_instance.run(command) }.to raise_error(QA::Support::Run::CommandError, /The command .* failed \(1\) with the following output:\nFAILURE/)
end
end
# frozen_string_literal: true
RSpec.describe QA::Support::SSH do
let(:key) { Struct.new(:private_key).new('private_key') }
let(:known_hosts_file) { Tempfile.new('known_hosts_file') }
let(:private_key_file) { Tempfile.new('private_key_file') }
let(:result) { QA::Support::Run::Result.new('', 0, '') }
let(:ssh) do
described_class.new.tap do |ssh|
ssh.uri = uri
ssh.key = key
ssh.private_key_file = private_key_file
ssh.known_hosts_file = known_hosts_file
end
end
shared_examples 'providing correct ports' do
context 'when no port specified in uri' do
let(:uri) { 'http://foo.com' }
it 'does not provide port in ssh command' do
expect(ssh).to receive(:run).with(expected_ssh_command_no_port, any_args).and_return(result)
call_method
end
end
context 'when port 80 specified in uri' do
let(:uri) { 'http://foo.com:80' }
it 'does not provide port in ssh command' do
expect(ssh).to receive(:run).with(expected_ssh_command_no_port, any_args).and_return(result)
call_method
end
end
context 'when other port is specified in uri' do
let(:port) { 1234 }
let(:uri) { "http://foo.com:#{port}" }
it "provides other port in ssh command" do
expect(ssh).to receive(:run).with(expected_ssh_command_port, any_args).and_return(result)
call_method
end
end
end
describe '#setup' do
let(:expected_ssh_command_no_port) { "ssh-keyscan -H foo.com >> #{known_hosts_file.path}" }
let(:expected_ssh_command_port) { "ssh-keyscan -H -p #{port} foo.com >> #{known_hosts_file.path}" }
let(:call_method) { ssh.setup }
before do
allow(File).to receive(:binwrite).with(private_key_file, key.private_key)
allow(File).to receive(:chmod).with(0700, private_key_file)
end
it_behaves_like 'providing correct ports'
end
describe '#reset_2fa_codes' do
let(:expected_ssh_command_no_port) { "echo yes | ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path} git@foo.com 2fa_recovery_codes" }
let(:expected_ssh_command_port) { "echo yes | ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path} git@foo.com -p #{port} 2fa_recovery_codes" }
let(:call_method) { ssh.reset_2fa_codes }
before do
allow(ssh).to receive(:git_user).and_return('git')
end
it_behaves_like 'providing correct ports'
end
describe '#git_user' do
context 'when running on CI' do
let(:uri) { 'http://gitlab.com' }
before do
allow(QA::Runtime::Env).to receive(:running_in_ci?).and_return(true)
end
it 'returns git user' do
expect(ssh.send(:git_user)).to eq('git')
end
end
context 'when running against environment on a port other than 80 or 443' do
let(:uri) { 'http://localhost:3000' }
before do
allow(Etc).to receive(:getlogin).and_return('dummy_username')
allow(QA::Runtime::Env).to receive(:running_in_ci?).and_return(false)
end
it 'returns the local user' do
expect(ssh.send(:git_user)).to eq('dummy_username')
end
end
context 'when running against environment on port 80 and not on CI (docker)' do
let(:uri) { 'http://localhost' }
before do
allow(QA::Runtime::Env).to receive(:running_in_ci?).and_return(false)
end
it 'returns git user' do
expect(ssh.send(:git_user)).to eq('git')
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