Commit 83fe18a3 authored by Mark Lapierre's avatar Mark Lapierre

Merge branch 'qa-shl-2fa-ssh-recovery-2' into 'master'

E2E tests for 2FA recovery via SSH

See merge request gitlab-org/gitlab!44114
parents 1299d1a4 a20d4391
......@@ -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