Commit c8bf2e7d authored by Nick Thomas's avatar Nick Thomas

Revert "Merge branch 'ash.mckenzie/srp-refactor' into 'master'"

This reverts commit 3aaf4751, reversing
changes made to c6577e0d.
parent 764f6f47
source "http://rubygems.org" source "http://rubygems.org"
group :development, :test do group :development, :test do
gem 'guard-rspec', '~> 4.0' gem 'guard', '~> 1.5.0'
gem 'listen', '~> 3.0.0' gem 'guard-rspec', '~> 2.1.0'
gem 'rspec', '~> 3.0' gem 'listen', '~> 0.5.0'
gem 'rspec', '~> 2.0'
gem 'rspec-its', '~> 1.0.0' gem 'rspec-its', '~> 1.0.0'
gem 'rubocop', '0.49.1', require: false gem 'rubocop', '0.49.1', require: false
gem 'simplecov', '~> 0.9.0', require: false gem 'simplecov', '~> 0.9.0', require: false
gem 'vcr', '~> 4.0' gem 'vcr', '~> 2.4.0'
gem 'webmock', '~> 1.9.0' gem 'webmock', '~> 1.9.0'
end end
...@@ -9,32 +9,18 @@ GEM ...@@ -9,32 +9,18 @@ GEM
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
diff-lcs (1.3) diff-lcs (1.3)
docile (1.1.5) docile (1.1.5)
ffi (1.9.25) guard (1.5.4)
formatador (0.2.5) listen (>= 0.4.2)
guard (2.14.2) lumberjack (>= 1.0.2)
formatador (>= 0.2.4) pry (>= 0.9.10)
listen (>= 2.7, < 4.0) thor (>= 0.14.6)
lumberjack (>= 1.0.12, < 2.0) guard-rspec (2.1.2)
nenv (~> 0.1) guard (>= 1.1)
notiffany (~> 0.0) rspec (~> 2.11)
pry (>= 0.9.12) listen (0.5.3)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-rspec (4.7.3)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
lumberjack (1.0.13) lumberjack (1.0.13)
method_source (0.9.0) method_source (0.9.0)
multi_json (1.13.1) multi_json (1.13.1)
nenv (0.3.0)
notiffany (0.1.1)
nenv (~> 0.1)
shellany (~> 0.0)
parallel (1.12.1) parallel (1.12.1)
parser (2.5.1.2) parser (2.5.1.2)
ast (~> 2.4.0) ast (~> 2.4.0)
...@@ -46,25 +32,17 @@ GEM ...@@ -46,25 +32,17 @@ GEM
rainbow (2.2.2) rainbow (2.2.2)
rake rake
rake (12.3.1) rake (12.3.1)
rb-fsevent (0.10.3) rspec (2.99.0)
rb-inotify (0.9.10) rspec-core (~> 2.99.0)
ffi (>= 0.5.0, < 2) rspec-expectations (~> 2.99.0)
rspec (3.7.0) rspec-mocks (~> 2.99.0)
rspec-core (~> 3.7.0) rspec-core (2.99.2)
rspec-expectations (~> 3.7.0) rspec-expectations (2.99.2)
rspec-mocks (~> 3.7.0) diff-lcs (>= 1.1.3, < 2.0)
rspec-core (3.7.1)
rspec-support (~> 3.7.0)
rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-its (1.0.1) rspec-its (1.0.1)
rspec-core (>= 2.99.0.beta1) rspec-core (>= 2.99.0.beta1)
rspec-expectations (>= 2.99.0.beta1) rspec-expectations (>= 2.99.0.beta1)
rspec-mocks (3.7.0) rspec-mocks (2.99.4)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-support (3.7.1)
rubocop (0.49.1) rubocop (0.49.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0) parser (>= 2.3.3.1, < 3.0)
...@@ -74,7 +52,6 @@ GEM ...@@ -74,7 +52,6 @@ GEM
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.9.0) ruby-progressbar (1.9.0)
safe_yaml (1.0.4) safe_yaml (1.0.4)
shellany (0.0.1)
simplecov (0.9.2) simplecov (0.9.2)
docile (~> 1.1.0) docile (~> 1.1.0)
multi_json (~> 1.0) multi_json (~> 1.0)
...@@ -82,7 +59,7 @@ GEM ...@@ -82,7 +59,7 @@ GEM
simplecov-html (0.9.0) simplecov-html (0.9.0)
thor (0.20.0) thor (0.20.0)
unicode-display_width (1.4.0) unicode-display_width (1.4.0)
vcr (4.0.0) vcr (2.4.0)
webmock (1.9.3) webmock (1.9.3)
addressable (>= 2.2.7) addressable (>= 2.2.7)
crack (>= 0.3.2) crack (>= 0.3.2)
...@@ -91,13 +68,14 @@ PLATFORMS ...@@ -91,13 +68,14 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
guard-rspec (~> 4.0) guard (~> 1.5.0)
listen (~> 3.0.0) guard-rspec (~> 2.1.0)
rspec (~> 3.0) listen (~> 0.5.0)
rspec (~> 2.0)
rspec-its (~> 1.0.0) rspec-its (~> 1.0.0)
rubocop (= 0.49.1) rubocop (= 0.49.1)
simplecov (~> 0.9.0) simplecov (~> 0.9.0)
vcr (~> 4.0) vcr (~> 2.4.0)
webmock (~> 1.9.0) webmock (~> 1.9.0)
BUNDLED WITH BUNDLED WITH
......
# A sample Guardfile
# More info at https://github.com/guard/guard#readme # More info at https://github.com/guard/guard#readme
guard :rspec, cmd: 'bundle exec rspec' do guard 'rspec' do
watch(%r{^spec/.+_spec\.rb$}) watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" } watch('spec/spec_helper.rb') { "spec" }
# Rails example
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
watch('config/routes.rb') { "spec/routing" }
watch('app/controllers/application_controller.rb') { "spec/controllers" }
# Capybara features specs
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
# Turnip features and steps
watch(%r{^spec/acceptance/(.+)\.feature$})
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
end end
...@@ -10,16 +10,16 @@ ...@@ -10,16 +10,16 @@
# command="/bin/gitlab-shell key-#",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQA... # command="/bin/gitlab-shell key-#",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQA...
# #
full_key = ARGV[0] key = ARGV[0]
abort "# No key provided" if full_key.nil? || full_key.empty? abort "# No key provided" if key.nil? || key.empty?
require_relative "../lib/gitlab_init" require_relative "../lib/gitlab_init"
require_relative "../lib/gitlab_net" require_relative "../lib/gitlab_net"
require_relative "../lib/gitlab_keys" require_relative "../lib/gitlab_keys"
authorized_key = GitlabNet.new.authorized_key(full_key) authorized_key = GitlabNet.new.authorized_key(key)
if authorized_key.nil? if authorized_key.nil?
puts "# No key was found for #{full_key}" puts "# No key was found for #{key}"
else else
puts GitlabKeys.key_line("key-#{authorized_key['id']}", authorized_key['key']) puts GitlabKeys.key_line("key-#{authorized_key['id']}", authorized_key["key"])
end end
...@@ -13,7 +13,7 @@ require_relative '../lib/gitlab_init' ...@@ -13,7 +13,7 @@ require_relative '../lib/gitlab_init'
# /bin/gitlab-keys rm-key key-23 "ssh-rsa AAAAx321..." # /bin/gitlab-keys rm-key key-23 "ssh-rsa AAAAx321..."
# #
# /bin/gitlab-keys list-keys # /bin/gitlab-keys list-keys
# #
# /bin/gitlab-keys clear # /bin/gitlab-keys clear
# #
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
# command for a given ssh key fingerprint # command for a given ssh key fingerprint
# #
# Ex. # Ex.
# bin/gitlab-shell-authorized-keys-check <expected-username> <actual-username> <public-key> # bin/gitlab-shell-authorized-keys-check <username> <public-key>
# #
# Returns # Returns
# command="/bin/gitlab-shell key-#",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAA... # command="/bin/gitlab-shell key-#",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAA...
...@@ -27,16 +27,16 @@ abort '# No username provided' if actual_username.nil? || actual_username == '' ...@@ -27,16 +27,16 @@ abort '# No username provided' if actual_username.nil? || actual_username == ''
# Normally, these would both be 'git', but it can be configured by the user # Normally, these would both be 'git', but it can be configured by the user
exit 0 unless expected_username == actual_username exit 0 unless expected_username == actual_username
full_key = ARGV[2] key = ARGV[2]
abort "# No key provided" if full_key.nil? || full_key == '' abort "# No key provided" if key.nil? || key == ''
require_relative '../lib/gitlab_init' require_relative '../lib/gitlab_init'
require_relative '../lib/gitlab_net' require_relative '../lib/gitlab_net'
require_relative '../lib/gitlab_keys' require_relative '../lib/gitlab_keys'
authorized_key = GitlabNet.new.authorized_key(full_key) authorized_key = GitlabNet.new.authorized_key(key)
if authorized_key.nil? if authorized_key.nil?
puts "# No key was found for #{full_key}" puts "# No key was found for #{key}"
else else
puts GitlabKeys.key_line("key-#{authorized_key['id']}", authorized_key['key']) puts GitlabKeys.key_line("key-#{authorized_key['id']}", authorized_key['key'])
end end
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
# the right options. # the right options.
# #
# Ex. # Ex.
# bin/gitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...] # bin/gitlab-shell-authorized-keys-check <key-id> <principal1> [<principal2>...]
# #
# Returns one line per principal passed in, e.g.: # Returns one line per principal passed in, e.g.:
# command="/bin/gitlab-shell username-{KEY_ID}",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty {PRINCIPAL} # command="/bin/gitlab-shell username-{KEY_ID}",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty {PRINCIPAL}
...@@ -23,9 +23,9 @@ key_id = ARGV[0] ...@@ -23,9 +23,9 @@ key_id = ARGV[0]
abort '# No key_id provided' if key_id.nil? || key_id == '' abort '# No key_id provided' if key_id.nil? || key_id == ''
principals = ARGV[1..-1] principals = ARGV[1..-1]
principals.each do |principal| principals.each { |principal|
abort '# An invalid principal was provided' if principal.nil? || principal == '' abort '# An invalid principal was provided' if principal.nil? || principal == ''
end }
require_relative '../lib/gitlab_init' require_relative '../lib/gitlab_init'
require_relative '../lib/gitlab_net' require_relative '../lib/gitlab_net'
......
...@@ -4,15 +4,15 @@ ...@@ -4,15 +4,15 @@
# will be processed properly. # will be processed properly.
refs = $stdin.read refs = $stdin.read
gl_id = ENV.delete('GL_ID') key_id = ENV.delete('GL_ID')
gl_repository = ENV['GL_REPOSITORY'] gl_repository = ENV['GL_REPOSITORY']
repo_path = Dir.pwd repo_path = Dir.pwd
require_relative '../lib/gitlab_custom_hook' require_relative '../lib/gitlab_custom_hook'
require_relative '../lib/gitlab_post_receive' require_relative '../lib/gitlab_post_receive'
if GitlabPostReceive.new(gl_repository, repo_path, gl_id, refs).exec && if GitlabPostReceive.new(gl_repository, repo_path, key_id, refs).exec &&
GitlabCustomHook.new(repo_path, gl_id).post_receive(refs) GitlabCustomHook.new(repo_path, key_id).post_receive(refs)
exit 0 exit 0
else else
exit 1 exit 1
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
# will be processed properly. # will be processed properly.
refs = $stdin.read refs = $stdin.read
gl_id = ENV.delete('GL_ID') key_id = ENV.delete('GL_ID')
protocol = ENV.delete('GL_PROTOCOL') protocol = ENV.delete('GL_PROTOCOL')
repo_path = Dir.pwd repo_path = Dir.pwd
gl_repository = ENV['GL_REPOSITORY'] gl_repository = ENV['GL_REPOSITORY']
...@@ -23,8 +23,8 @@ require_relative '../lib/gitlab_net' ...@@ -23,8 +23,8 @@ require_relative '../lib/gitlab_net'
# last so that it only runs if everything else succeeded. On post-receive on the # last so that it only runs if everything else succeeded. On post-receive on the
# other hand, we run GitlabPostReceive first because the push is already done # other hand, we run GitlabPostReceive first because the push is already done
# and we don't want to skip it if the custom hook fails. # and we don't want to skip it if the custom hook fails.
if GitlabAccess.new(gl_repository, repo_path, gl_id, refs, protocol).exec && if GitlabAccess.new(gl_repository, repo_path, key_id, refs, protocol).exec &&
GitlabCustomHook.new(repo_path, gl_id).pre_receive(refs) && GitlabCustomHook.new(repo_path, key_id).pre_receive(refs) &&
increase_reference_counter(gl_repository, repo_path) increase_reference_counter(gl_repository, repo_path)
exit 0 exit 0
else else
......
...@@ -7,11 +7,11 @@ ref_name = ARGV[0] ...@@ -7,11 +7,11 @@ ref_name = ARGV[0]
old_value = ARGV[1] old_value = ARGV[1]
new_value = ARGV[2] new_value = ARGV[2]
repo_path = Dir.pwd repo_path = Dir.pwd
gl_id = ENV.delete('GL_ID') key_id = ENV.delete('GL_ID')
require_relative '../lib/gitlab_custom_hook' require_relative '../lib/gitlab_custom_hook'
if GitlabCustomHook.new(repo_path, gl_id).update(ref_name, old_value, new_value) if GitlabCustomHook.new(repo_path, key_id).update(ref_name, old_value, new_value)
exit 0 exit 0
else else
exit 1 exit 1
......
require_relative 'action/base'
require_relative 'action/gitaly'
require_relative 'action/git_lfs_authenticate'
require_relative 'action/api_2fa_recovery'
module Action
end
require_relative '../action'
require_relative '../gitlab_logger'
module Action
class API2FARecovery < Base
def initialize(actor)
@actor = actor
end
def execute(_, _)
recover
end
private
attr_reader :actor
def continue?(question)
puts "#{question} (yes/no)"
STDOUT.flush # Make sure the question gets output before we wait for input
response = STDIN.gets.chomp
puts '' # Add a buffer in the output
response == 'yes'
end
def recover
continue = continue?(
"Are you sure you want to generate new two-factor recovery codes?\n" \
"Any existing recovery codes you saved will be invalidated."
)
unless continue
puts 'New recovery codes have *not* been generated. Existing codes will remain valid.'
return
end
resp = api.two_factor_recovery_codes(actor)
if resp['success']
codes = resp['recovery_codes'].join("\n")
$logger.info('API 2FA recovery success', user: actor.log_username)
puts "Your two-factor authentication recovery codes are:\n\n" \
"#{codes}\n\n" \
"During sign in, use one of the codes above when prompted for\n" \
"your two-factor code. Then, visit your Profile Settings and add\n" \
"a new device so you do not lose access to your account again."
true
else
$logger.info('API 2FA recovery error', user: actor.log_username)
puts "An error occurred while trying to generate new recovery codes.\n" \
"#{resp['message']}"
end
end
end
end
require 'json'
require_relative '../gitlab_config'
require_relative '../gitlab_net'
require_relative '../gitlab_metrics'
module Action
class Base
def initialize
raise NotImplementedError
end
def self.create_from_json(_)
raise NotImplementedError
end
private
def config
@config ||= GitlabConfig.new
end
def api
@api ||= GitlabNet.new
end
end
end
require_relative '../action'
require_relative '../gitlab_logger'
module Action
class GitLFSAuthenticate < Base
def initialize(actor, repo_name)
@actor = actor
@repo_name = repo_name
end
def execute(_, _)
GitlabMetrics.measure('lfs-authenticate') do
$logger.info('Processing LFS authentication', user: actor.log_username)
lfs_access = api.lfs_authenticate(actor, repo_name)
return unless lfs_access
puts lfs_access.authentication_payload
end
true
end
private
attr_reader :actor, :repo_name
end
end
require_relative '../action'
require_relative '../gitlab_logger'
require_relative '../gitlab_net'
module Action
class Gitaly < Base
REPOSITORY_PATH_NOT_PROVIDED = "Repository path not provided. Please make sure you're using GitLab v8.10 or later.".freeze
MIGRATED_COMMANDS = {
'git-upload-pack' => File.join(ROOT_PATH, 'bin', 'gitaly-upload-pack'),
'git-upload-archive' => File.join(ROOT_PATH, 'bin', 'gitaly-upload-archive'),
'git-receive-pack' => File.join(ROOT_PATH, 'bin', 'gitaly-receive-pack')
}.freeze
def initialize(actor, gl_repository, gl_username, git_protocol, repository_path, gitaly)
@actor = actor
@gl_repository = gl_repository
@gl_username = gl_username
@git_protocol = git_protocol
@repository_path = repository_path
@gitaly = gitaly
end
def self.create_from_json(actor, json)
new(actor,
json['gl_repository'],
json['gl_username'],
json['git_protocol'],
json['repository_path'],
json['gitaly'])
end
def execute(command, args)
raise ArgumentError, REPOSITORY_PATH_NOT_PROVIDED unless repository_path
raise InvalidRepositoryPathError unless valid_repository?
$logger.info('Performing Gitaly command', user: actor.log_username)
process(command, args)
end
private
attr_reader :actor, :gl_repository, :gl_username, :repository_path, :gitaly
def git_protocol
@git_protocol || ENV['GIT_PROTOCOL'] # TODO: tidy this up
end
def process(command, args)
executable = command
args = [repository_path]
if MIGRATED_COMMANDS.key?(executable) && gitaly
executable = MIGRATED_COMMANDS[executable]
gitaly_address = gitaly['address']
args = [gitaly_address, JSON.dump(gitaly_request)]
end
args_string = [File.basename(executable), *args].join(' ')
$logger.info('executing git command', command: args_string, user: actor.log_username)
exec_cmd(executable, *args)
end
def exec_cmd(*args)
env = exec_env
env['GITALY_TOKEN'] = gitaly['token'] if gitaly && gitaly.include?('token')
if git_trace_available?
env.merge!(
'GIT_TRACE' => config.git_trace_log_file,
'GIT_TRACE_PACKET' => config.git_trace_log_file,
'GIT_TRACE_PERFORMANCE' => config.git_trace_log_file
)
end
# We use 'chdir: ROOT_PATH' to let the next executable know where config.yml is.
Kernel.exec(env, *args, unsetenv_others: true, chdir: ROOT_PATH)
end
def exec_env
{
'HOME' => ENV['HOME'],
'PATH' => ENV['PATH'],
'LD_LIBRARY_PATH' => ENV['LD_LIBRARY_PATH'],
'LANG' => ENV['LANG'],
'GL_ID' => actor.identifier,
'GL_PROTOCOL' => GitlabNet::GL_PROTOCOL,
'GL_REPOSITORY' => gl_repository,
'GL_USERNAME' => gl_username
}
end
def gitaly_request
# The entire gitaly_request hash should be built in gitlab-ce and passed
# on as-is. For now we build a fake one on the spot.
{
'repository' => gitaly['repository'],
'gl_repository' => gl_repository,
'gl_id' => actor.identifier,
'gl_username' => gl_username,
'git_protocol' => git_protocol
}
end
def valid_repository?
File.absolute_path(repository_path) == repository_path
end
def git_trace_available?
return false unless config.git_trace_log_file
if Pathname(config.git_trace_log_file).relative?
$logger.warn('git trace log path must be absolute, ignoring', git_trace_log_file: config.git_trace_log_file)
return false
end
begin
File.open(config.git_trace_log_file, 'a') { nil }
return true
rescue => ex
$logger.warn('Failed to open git trace log file', git_trace_log_file: config.git_trace_log_file, error: ex.to_s)
return false
end
end
end
end
require_relative 'actor/base'
require_relative 'actor/key'
require_relative 'actor/user'
require_relative 'actor/username'
module Actor
class UnsupportedActorError < StandardError; end
def self.new_from(str, audit_usernames: false)
case str
when Key.id_regex
Key.from(str, audit_usernames: audit_usernames)
when User.id_regex
User.from(str, audit_usernames: audit_usernames)
when Username.id_regex
Username.from(str, audit_usernames: audit_usernames)
else
raise UnsupportedActorError
end
end
end
module Actor
class Base
attr_reader :id
def initialize(id, audit_usernames: false)
@id = id
@audit_usernames = audit_usernames
end
def self.from(str, audit_usernames: false)
new(str.gsub(/#{identifier_prefix}-/, ''), audit_usernames: audit_usernames)
end
def self.identifier_key
raise NotImplementedError
end
def self.identifier_prefix
raise NotImplementedError
end
def self.id_regex
raise NotImplementedError
end
def username
raise NotImplementedError
end
def identifier
"#{self.class.identifier_prefix}-#{id}"
end
def identifier_key
self.class.identifier_key
end
def log_username
audit_usernames? ? username : "#{label} with identifier #{identifier}"
end
private
attr_reader :audit_usernames
alias audit_usernames? audit_usernames
def klass_name
self.class.to_s.split('::')[-1]
end
def label
klass_name.downcase
end
end
end
require_relative 'base'
require_relative '../gitlab_net'
module Actor
class Key < Base
ANONYMOUS_USER = 'Anonymous'.freeze
alias key_id id
def self.identifier_prefix
'key'.freeze
end
def self.identifier_key
'key_id'.freeze
end
def self.id_regex
/\Akey\-\d+\Z/
end
def username
@username ||= begin
user = GitlabNet.new.discover(self)
user ? "@#{user['username']}" : ANONYMOUS_USER
end
end
end
end
require_relative 'base'
module Actor
class User < Base
alias username identifier
def self.identifier_prefix
'user'.freeze
end
def self.identifier_key
'user_id'.freeze
end
def self.id_regex
/\Auser\-\d+\Z/
end
end
end
require_relative 'base'
require_relative 'key'
module Actor
class Username < Key
def self.identifier_prefix
'username'.freeze
end
def self.identifier_key
'username'.freeze
end
def self.id_regex
/\Ausername\-[a-z0-9-]+\z/
end
private
# Override Base#label
def label
'user'
end
end
end
class UnknownError < StandardError; end
class AccessDeniedError < StandardError; end
class InvalidRepositoryPathError < StandardError; end
class DisallowedCommandError < StandardError; end
require 'json'
require_relative 'errors'
require_relative 'actor'
require_relative 'gitlab_init' require_relative 'gitlab_init'
require_relative 'gitlab_net' require_relative 'gitlab_net'
require_relative 'gitlab_access_status'
require_relative 'names_helper' require_relative 'names_helper'
require_relative 'gitlab_metrics' require_relative 'gitlab_metrics'
require_relative 'object_dirs_helper' require_relative 'object_dirs_helper'
require 'json'
class GitlabAccess class GitlabAccess
class AccessDeniedError < StandardError; end
include NamesHelper include NamesHelper
def initialize(gl_repository, repo_path, gl_id, changes, protocol) attr_reader :config, :gl_repository, :repo_path, :changes, :protocol
def initialize(gl_repository, repo_path, actor, changes, protocol)
@config = GitlabConfig.new
@gl_repository = gl_repository @gl_repository = gl_repository
@repo_path = repo_path.strip @repo_path = repo_path.strip
@gl_id = gl_id @actor = actor
@changes = changes.lines @changes = changes.lines
@protocol = protocol @protocol = protocol
end end
def exec def exec
GitlabMetrics.measure('check-access:git-receive-pack') do status = GitlabMetrics.measure('check-access:git-receive-pack') do
api.check_access('git-receive-pack', gl_repository, repo_path, actor, changes, protocol, env: ObjectDirsHelper.all_attributes.to_json) api.check_access('git-receive-pack', @gl_repository, @repo_path, @actor, @changes, @protocol, env: ObjectDirsHelper.all_attributes.to_json)
end end
raise AccessDeniedError, status.message unless status.allowed?
true true
rescue GitlabNet::ApiUnreachableError rescue GitlabNet::ApiUnreachableError
$stderr.puts "GitLab: Failed to authorize your Git request: internal API unreachable" $stderr.puts "GitLab: Failed to authorize your Git request: internal API unreachable"
...@@ -32,19 +38,9 @@ class GitlabAccess ...@@ -32,19 +38,9 @@ class GitlabAccess
false false
end end
private protected
attr_reader :gl_repository, :repo_path, :gl_id, :changes, :protocol
def api def api
@api ||= GitlabNet.new GitlabNet.new
end
def config
@config ||= GitlabConfig.new
end
def actor
@actor ||= Actor.new_from(gl_id, audit_usernames: config.audit_usernames)
end end
end end
require 'json'
class GitAccessStatus
attr_reader :message, :gl_repository, :gl_id, :gl_username, :repository_path, :gitaly, :git_protocol
def initialize(status, message, gl_repository:, gl_id:, gl_username:, repository_path:, gitaly:, git_protocol:)
@status = status
@message = message
@gl_repository = gl_repository
@gl_id = gl_id
@gl_username = gl_username
@repository_path = repository_path
@gitaly = gitaly
@git_protocol = git_protocol
end
def self.create_from_json(json)
values = JSON.parse(json)
new(values["status"],
values["message"],
gl_repository: values["gl_repository"],
gl_id: values["gl_id"],
gl_username: values["gl_username"],
repository_path: values["repository_path"],
gitaly: values["gitaly"],
git_protocol: values["git_protocol"])
end
def allowed?
@status
end
end
...@@ -5,9 +5,9 @@ require_relative 'gitlab_metrics' ...@@ -5,9 +5,9 @@ require_relative 'gitlab_metrics'
class GitlabCustomHook class GitlabCustomHook
attr_reader :vars, :config attr_reader :vars, :config
def initialize(repo_path, gl_id) def initialize(repo_path, key_id)
@repo_path = repo_path @repo_path = repo_path
@vars = { 'GL_ID' => gl_id } @vars = { 'GL_ID' => key_id }
@config = GitlabConfig.new @config = GitlabConfig.new
end end
......
...@@ -9,7 +9,6 @@ class GitlabKeys # rubocop:disable Metrics/ClassLength ...@@ -9,7 +9,6 @@ class GitlabKeys # rubocop:disable Metrics/ClassLength
attr_accessor :auth_file, :key attr_accessor :auth_file, :key
# TODO: whatever is not a great name
def self.command(whatever) def self.command(whatever)
"#{ROOT_PATH}/bin/gitlab-shell #{whatever}" "#{ROOT_PATH}/bin/gitlab-shell #{whatever}"
end end
...@@ -22,7 +21,6 @@ class GitlabKeys # rubocop:disable Metrics/ClassLength ...@@ -22,7 +21,6 @@ class GitlabKeys # rubocop:disable Metrics/ClassLength
command(key_id) command(key_id)
end end
# TODO: whatever is not a great name
def self.whatever_line(command, trailer) def self.whatever_line(command, trailer)
"command=\"#{command}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{trailer}" "command=\"#{command}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{trailer}"
end end
......
require 'net/http'
require 'openssl'
require 'json' require 'json'
require_relative 'errors' require_relative 'gitlab_config'
require_relative 'gitlab_logger' require_relative 'gitlab_logger'
require_relative 'gitlab_access' require_relative 'gitlab_access'
require_relative 'gitlab_lfs_authentication' require_relative 'gitlab_lfs_authentication'
require_relative 'httpunix'
require_relative 'http_helper' require_relative 'http_helper'
require_relative 'action'
class GitlabNet class GitlabNet # rubocop:disable Metrics/ClassLength
include HTTPHelper include HTTPHelper
class ApiUnreachableError < StandardError; end
class NotFound < StandardError; end
CHECK_TIMEOUT = 5 CHECK_TIMEOUT = 5
GL_PROTOCOL = 'ssh'.freeze
API_INACCESSIBLE_ERROR = 'API is not accessible'.freeze
def check_access(cmd, gl_repository, repo, actor, changes, protocol = GL_PROTOCOL, env: {}) def check_access(cmd, gl_repository, repo, who, changes, protocol, env: {})
changes = changes.join("\n") unless changes.is_a?(String) changes = changes.join("\n") unless changes.is_a?(String)
params = { params = {
...@@ -26,27 +29,56 @@ class GitlabNet ...@@ -26,27 +29,56 @@ class GitlabNet
env: env env: env
} }
params[actor.identifier_key.to_sym] = actor.id who_sym, _, who_v = self.class.parse_who(who)
params[who_sym] = who_v
resp = post("#{internal_api_endpoint}/allowed", params) url = "#{internal_api_endpoint}/allowed"
resp = post(url, params)
determine_action(actor, resp) if resp.code == '200'
GitAccessStatus.create_from_json(resp.body)
else
GitAccessStatus.new(false,
'API is not accessible',
gl_repository: nil,
gl_id: nil,
gl_username: nil,
repository_path: nil,
gitaly: nil,
git_protocol: nil)
end
end end
def discover(actor) def discover(who)
resp = get("#{internal_api_endpoint}/discover?#{actor.identifier_key}=#{actor.id}") _, who_k, who_v = self.class.parse_who(who)
JSON.parse(resp.body) if resp.code == HTTP_SUCCESS
rescue JSON::ParserError, ApiUnreachableError resp = get("#{internal_api_endpoint}/discover?#{who_k}=#{who_v}")
nil
JSON.parse(resp.body) rescue nil
end end
def lfs_authenticate(actor, repo) def lfs_authenticate(gl_id, repo)
params = { project: sanitize_path(repo) } id_sym, _, id = self.class.parse_who(gl_id)
params[actor.identifier_key.to_sym] = actor.id
if id_sym == :key_id
params = {
project: sanitize_path(repo),
key_id: id
}
elsif id_sym == :user_id
params = {
project: sanitize_path(repo),
user_id: id
}
else
raise ArgumentError, "lfs_authenticate() got unsupported GL_ID='#{gl_id}'!"
end
resp = post("#{internal_api_endpoint}/lfs_authenticate", params) resp = post("#{internal_api_endpoint}/lfs_authenticate", params)
GitlabLfsAuthentication.build_from_json(resp.body) if resp.code == HTTP_SUCCESS if resp.code == '200'
GitlabLfsAuthentication.build_from_json(resp.body)
end
end end
def broadcast_message def broadcast_message
...@@ -61,7 +93,11 @@ class GitlabNet ...@@ -61,7 +93,11 @@ class GitlabNet
url += "&gl_repository=#{URI.escape(gl_repository)}" if gl_repository url += "&gl_repository=#{URI.escape(gl_repository)}" if gl_repository
resp = get(url) resp = get(url)
resp.code == HTTP_SUCCESS ? JSON.parse(resp.body) : [] if resp.code == '200'
JSON.parse(resp.body)
else
[]
end
rescue rescue
[] []
end end
...@@ -70,17 +106,19 @@ class GitlabNet ...@@ -70,17 +106,19 @@ class GitlabNet
get("#{internal_api_endpoint}/check", options: { read_timeout: CHECK_TIMEOUT }) get("#{internal_api_endpoint}/check", options: { read_timeout: CHECK_TIMEOUT })
end end
def authorized_key(full_key) def authorized_key(key)
resp = get("#{internal_api_endpoint}/authorized_keys?key=#{URI.escape(full_key, '+/=')}") resp = get("#{internal_api_endpoint}/authorized_keys?key=#{URI.escape(key, '+/=')}")
JSON.parse(resp.body) if resp.code == HTTP_SUCCESS JSON.parse(resp.body) if resp.code == "200"
rescue rescue
nil nil
end end
def two_factor_recovery_codes(actor) def two_factor_recovery_codes(gl_id)
params = { actor.identifier_key.to_sym => actor.id } id_sym, _, id = self.class.parse_who(gl_id)
resp = post("#{internal_api_endpoint}/two_factor_recovery_codes", params)
JSON.parse(resp.body) if resp.code == HTTP_SUCCESS resp = post("#{internal_api_endpoint}/two_factor_recovery_codes", id_sym => id)
JSON.parse(resp.body) if resp.code == '200'
rescue rescue
{} {}
end end
...@@ -89,50 +127,51 @@ class GitlabNet ...@@ -89,50 +127,51 @@ class GitlabNet
params = { gl_repository: gl_repository, project: repo_path } params = { gl_repository: gl_repository, project: repo_path }
resp = post("#{internal_api_endpoint}/notify_post_receive", params) resp = post("#{internal_api_endpoint}/notify_post_receive", params)
resp.code == HTTP_SUCCESS resp.code == '200'
rescue rescue
false false
end end
def post_receive(gl_repository, actor, changes) def post_receive(gl_repository, identifier, changes)
params = { gl_repository: gl_repository, identifier: actor.identifier, changes: changes } params = {
gl_repository: gl_repository,
identifier: identifier,
changes: changes
}
resp = post("#{internal_api_endpoint}/post_receive", params) resp = post("#{internal_api_endpoint}/post_receive", params)
raise NotFoundError if resp.code == HTTP_NOT_FOUND
JSON.parse(resp.body) if resp.code == HTTP_SUCCESS raise NotFound if resp.code == '404'
JSON.parse(resp.body) if resp.code == '200'
end end
def pre_receive(gl_repository) def pre_receive(gl_repository)
resp = post("#{internal_api_endpoint}/pre_receive", gl_repository: gl_repository) resp = post("#{internal_api_endpoint}/pre_receive", gl_repository: gl_repository)
raise NotFoundError if resp.code == HTTP_NOT_FOUND
JSON.parse(resp.body) if resp.code == HTTP_SUCCESS raise NotFound if resp.code == '404'
end
private JSON.parse(resp.body) if resp.code == '200'
def sanitize_path(repo)
repo.delete("'")
end end
def determine_action(actor, resp) def self.parse_who(who)
json = JSON.parse(resp.body) if who.start_with?("key-")
message = json['message'] value = who.gsub("key-", "")
raise ArgumentError, "who='#{who}' is invalid!" unless value =~ /\A[0-9]+\z/
case resp.code [:key_id, 'key_id', value]
when HTTP_SUCCESS elsif who.start_with?("user-")
# TODO: This raise can be removed once internal API can respond with correct value = who.gsub("user-", "")
# HTTP status codes, instead of relying upon parsing the body and raise ArgumentError, "who='#{who}' is invalid!" unless value =~ /\A[0-9]+\z/
# accessing the 'status' key. [:user_id, 'user_id', value]
raise AccessDeniedError, message unless json['status'] elsif who.start_with?("username-")
[:username, 'username', who.gsub("username-", "")]
Action::Gitaly.create_from_json(actor, json)
when HTTP_UNAUTHORIZED, HTTP_NOT_FOUND
raise AccessDeniedError, message
else else
raise UnknownError, "#{API_INACCESSIBLE_ERROR}: #{message}" raise ArgumentError, "who='#{who}' is invalid!"
end end
rescue JSON::ParserError end
raise UnknownError, API_INACCESSIBLE_ERROR
protected
def sanitize_path(repo)
repo.delete("'")
end end
end end
...@@ -8,18 +8,20 @@ require 'securerandom' ...@@ -8,18 +8,20 @@ require 'securerandom'
class GitlabPostReceive class GitlabPostReceive
include NamesHelper include NamesHelper
def initialize(gl_repository, repo_path, gl_id, changes) attr_reader :config, :gl_repository, :repo_path, :changes, :jid
def initialize(gl_repository, repo_path, actor, changes)
@config = GitlabConfig.new @config = GitlabConfig.new
@gl_repository = gl_repository @gl_repository = gl_repository
@repo_path = repo_path.strip @repo_path = repo_path.strip
@gl_id = gl_id @actor = actor
@changes = changes @changes = changes
@jid = SecureRandom.hex(12) @jid = SecureRandom.hex(12)
end end
def exec def exec
response = GitlabMetrics.measure("post-receive") do response = GitlabMetrics.measure("post-receive") do
api.post_receive(gl_repository, actor, changes) api.post_receive(gl_repository, @actor, changes)
end end
return false unless response return false unless response
...@@ -33,18 +35,12 @@ class GitlabPostReceive ...@@ -33,18 +35,12 @@ class GitlabPostReceive
false false
end end
private protected
attr_reader :config, :gl_repository, :repo_path, :gl_id, :changes, :jid
def api def api
@api ||= GitlabNet.new @api ||= GitlabNet.new
end end
def actor
@actor ||= Actor.new_from(gl_id, audit_usernames: config.audit_usernames)
end
def print_merge_request_links(merge_request_urls) def print_merge_request_links(merge_request_urls)
return if merge_request_urls.empty? return if merge_request_urls.empty?
puts puts
...@@ -104,6 +100,8 @@ class GitlabPostReceive ...@@ -104,6 +100,8 @@ class GitlabPostReceive
puts "=" * total_width puts "=" * total_width
end end
private
def parse_broadcast_msg(msg, text_length) def parse_broadcast_msg(msg, text_length)
msg ||= "" msg ||= ""
# just return msg if shorter than or equal to text length # just return msg if shorter than or equal to text length
......
...@@ -3,120 +3,299 @@ require 'pathname' ...@@ -3,120 +3,299 @@ require 'pathname'
require_relative 'gitlab_net' require_relative 'gitlab_net'
require_relative 'gitlab_metrics' require_relative 'gitlab_metrics'
require_relative 'actor'
class GitlabShell class GitlabShell # rubocop:disable Metrics/ClassLength
API_2FA_RECOVERY_CODES_COMMAND = '2fa_recovery_codes'.freeze class AccessDeniedError < StandardError; end
class DisallowedCommandError < StandardError; end
class InvalidRepositoryPathError < StandardError; end
GIT_UPLOAD_PACK_COMMAND = 'git-upload-pack'.freeze GIT_COMMANDS = %w(git-upload-pack git-receive-pack git-upload-archive git-lfs-authenticate).freeze
GIT_RECEIVE_PACK_COMMAND = 'git-receive-pack'.freeze GITALY_MIGRATED_COMMANDS = {
GIT_UPLOAD_ARCHIVE_COMMAND = 'git-upload-archive'.freeze 'git-upload-pack' => File.join(ROOT_PATH, 'bin', 'gitaly-upload-pack'),
GIT_LFS_AUTHENTICATE_COMMAND = 'git-lfs-authenticate'.freeze 'git-upload-archive' => File.join(ROOT_PATH, 'bin', 'gitaly-upload-archive'),
'git-receive-pack' => File.join(ROOT_PATH, 'bin', 'gitaly-receive-pack')
}.freeze
API_COMMANDS = %w(2fa_recovery_codes).freeze
GL_PROTOCOL = 'ssh'.freeze
GIT_COMMANDS = [GIT_UPLOAD_PACK_COMMAND, GIT_RECEIVE_PACK_COMMAND, attr_accessor :gl_id, :gl_repository, :repo_name, :command, :git_access, :git_protocol
GIT_UPLOAD_ARCHIVE_COMMAND, GIT_LFS_AUTHENTICATE_COMMAND].freeze attr_reader :repo_path
Struct.new('ParsedCommand', :command, :git_access_command, :repo_name, :args)
def initialize(who) def initialize(who)
who_sym, = GitlabNet.parse_who(who)
if who_sym == :username
@who = who
else
@gl_id = who
end
@config = GitlabConfig.new @config = GitlabConfig.new
@actor = Actor.new_from(who, audit_usernames: @config.audit_usernames)
end end
# The origin_cmd variable contains UNTRUSTED input. If the user ran # The origin_cmd variable contains UNTRUSTED input. If the user ran
# ssh git@gitlab.example.com 'evil command', then origin_cmd contains # ssh git@gitlab.example.com 'evil command', then origin_cmd contains
# 'evil command'. # 'evil command'.
def exec(origin_cmd) def exec(origin_cmd)
if !origin_cmd || origin_cmd.empty? unless origin_cmd
puts "Welcome to GitLab, #{actor.username}!" puts "Welcome to GitLab, #{username}!"
return true return true
end end
parsed_command = parse_cmd(origin_cmd) args = Shellwords.shellwords(origin_cmd)
action = determine_action(parsed_command) args = parse_cmd(args)
action.execute(parsed_command.command, parsed_command.args)
if GIT_COMMANDS.include?(args.first)
GitlabMetrics.measure('verify-access') { verify_access }
elsif !defined?(@gl_id)
# We're processing an API command like 2fa_recovery_codes, but
# don't have a @gl_id yet, that means we're in the "username"
# mode and need to materialize it, calling the "user" method
# will do that and call the /discover method.
user
end
process_cmd(args)
true
rescue GitlabNet::ApiUnreachableError rescue GitlabNet::ApiUnreachableError
$stderr.puts "GitLab: Failed to authorize your Git request: internal API unreachable" $stderr.puts "GitLab: Failed to authorize your Git request: internal API unreachable"
false false
rescue AccessDeniedError, UnknownError => ex rescue AccessDeniedError => ex
$logger.warn('Access denied', command: origin_cmd, user: actor.log_username) $logger.warn('Access denied', command: origin_cmd, user: log_username)
$stderr.puts "GitLab: #{ex.message}" $stderr.puts "GitLab: #{ex.message}"
false false
rescue DisallowedCommandError rescue DisallowedCommandError
$logger.warn('Denied disallowed command', command: origin_cmd, user: actor.log_username) $logger.warn('Denied disallowed command', command: origin_cmd, user: log_username)
$stderr.puts 'GitLab: Disallowed command'
$stderr.puts "GitLab: Disallowed command"
false false
rescue InvalidRepositoryPathError rescue InvalidRepositoryPathError
$stderr.puts 'GitLab: Invalid repository path' $stderr.puts "GitLab: Invalid repository path"
false false
end end
private protected
attr_reader :config, :actor
def parse_cmd(cmd)
args = Shellwords.shellwords(cmd)
def parse_cmd(args)
# Handle Git for Windows 2.14 using "git upload-pack" instead of git-upload-pack # Handle Git for Windows 2.14 using "git upload-pack" instead of git-upload-pack
if args.length == 3 && args.first == 'git' if args.length == 3 && args.first == 'git'
command = "git-#{args[1]}" @command = "git-#{args[1]}"
args = [command, args.last] args = [@command, args.last]
else else
command = args.first @command = args.first
end end
git_access_command = command @git_access = @command
if command == API_2FA_RECOVERY_CODES_COMMAND return args if API_COMMANDS.include?(@command)
return Struct::ParsedCommand.new(command, git_access_command, nil, args)
end
raise DisallowedCommandError unless GIT_COMMANDS.include?(command) raise DisallowedCommandError unless GIT_COMMANDS.include?(@command)
case command case @command
when 'git-lfs-authenticate' when 'git-lfs-authenticate'
raise DisallowedCommandError unless args.count >= 2 raise DisallowedCommandError unless args.count >= 2
repo_name = args[1] @repo_name = args[1]
git_access_command = case args[2] case args[2]
when 'download' when 'download'
GIT_UPLOAD_PACK_COMMAND @git_access = 'git-upload-pack'
when 'upload' when 'upload'
GIT_RECEIVE_PACK_COMMAND @git_access = 'git-receive-pack'
else else
raise DisallowedCommandError raise DisallowedCommandError
end end
else else
raise DisallowedCommandError unless args.count == 2 raise DisallowedCommandError unless args.count == 2
repo_name = args.last @repo_name = args.last
end end
Struct::ParsedCommand.new(command, git_access_command, repo_name, args) args
end end
def determine_action(parsed_command) def verify_access
return Action::API2FARecovery.new(actor) if parsed_command.command == API_2FA_RECOVERY_CODES_COMMAND status = api.check_access(@git_access, nil, @repo_name, @who || @gl_id, '_any', GL_PROTOCOL)
raise AccessDeniedError, status.message unless status.allowed?
self.repo_path = status.repository_path
@gl_repository = status.gl_repository
@git_protocol = ENV['GIT_PROTOCOL']
@gitaly = status.gitaly
@username = status.gl_username
if defined?(@who)
@gl_id = status.gl_id
end
end
GitlabMetrics.measure('verify-access') do def process_cmd(args)
# GitlabNet#check_access will raise exception in the event of a problem return send("api_#{@command}") if API_COMMANDS.include?(@command)
initial_action = api.check_access(
parsed_command.git_access_command, if @command == 'git-lfs-authenticate'
nil, GitlabMetrics.measure('lfs-authenticate') do
parsed_command.repo_name, $logger.info('Processing LFS authentication', user: log_username)
actor, lfs_authenticate
'_any' end
return
end
executable = @command
args = [repo_path]
if GITALY_MIGRATED_COMMANDS.key?(executable) && @gitaly
executable = GITALY_MIGRATED_COMMANDS[executable]
gitaly_address = @gitaly['address']
# The entire gitaly_request hash should be built in gitlab-ce and passed
# on as-is. For now we build a fake one on the spot.
gitaly_request = {
'repository' => @gitaly['repository'],
'gl_repository' => @gl_repository,
'gl_id' => @gl_id,
'gl_username' => @username,
'git_protocol' => @git_protocol
}
args = [gitaly_address, JSON.dump(gitaly_request)]
end
args_string = [File.basename(executable), *args].join(' ')
$logger.info('executing git command', command: args_string, user: log_username)
exec_cmd(executable, *args)
end
# This method is not covered by Rspec because it ends the current Ruby process.
def exec_cmd(*args)
# If you want to call a command without arguments, use
# exec_cmd(['my_command', 'my_command']) . Otherwise use
# exec_cmd('my_command', 'my_argument', ...).
if args.count == 1 && !args.first.is_a?(Array)
raise DisallowedCommandError
end
env = {
'HOME' => ENV['HOME'],
'PATH' => ENV['PATH'],
'LD_LIBRARY_PATH' => ENV['LD_LIBRARY_PATH'],
'LANG' => ENV['LANG'],
'GL_ID' => @gl_id,
'GL_PROTOCOL' => GL_PROTOCOL,
'GL_REPOSITORY' => @gl_repository,
'GL_USERNAME' => @username
}
if @gitaly && @gitaly.include?('token')
env['GITALY_TOKEN'] = @gitaly['token']
end
if git_trace_available?
env.merge!(
'GIT_TRACE' => @config.git_trace_log_file,
'GIT_TRACE_PACKET' => @config.git_trace_log_file,
'GIT_TRACE_PERFORMANCE' => @config.git_trace_log_file
) )
end
case parsed_command.command # We use 'chdir: ROOT_PATH' to let the next executable know where config.yml is.
when GIT_LFS_AUTHENTICATE_COMMAND Kernel.exec(env, *args, unsetenv_others: true, chdir: ROOT_PATH)
Action::GitLFSAuthenticate.new(actor, parsed_command.repo_name) end
def api
GitlabNet.new
end
def user
return @user if defined?(@user)
begin
if defined?(@who)
@user = api.discover(@who)
@gl_id = "user-#{@user['id']}"
else else
initial_action @user = api.discover(@gl_id)
end end
rescue GitlabNet::ApiUnreachableError
@user = nil
end end
end end
def api def username_from_discover
@api ||= GitlabNet.new return nil unless user && user['username']
"@#{user['username']}"
end
def username
@username ||= username_from_discover || 'Anonymous'
end
# User identifier to be used in log messages.
def log_username
@config.audit_usernames ? username : "user with id #{@gl_id}"
end
def lfs_authenticate
lfs_access = api.lfs_authenticate(@gl_id, @repo_name)
return unless lfs_access
puts lfs_access.authentication_payload
end
private
def continue?(question)
puts "#{question} (yes/no)"
STDOUT.flush # Make sure the question gets output before we wait for input
continue = STDIN.gets.chomp
puts '' # Add a buffer in the output
continue == 'yes'
end
def api_2fa_recovery_codes
continue = continue?(
"Are you sure you want to generate new two-factor recovery codes?\n" \
"Any existing recovery codes you saved will be invalidated."
)
unless continue
puts 'New recovery codes have *not* been generated. Existing codes will remain valid.'
return
end
resp = api.two_factor_recovery_codes(@gl_id)
if resp['success']
codes = resp['recovery_codes'].join("\n")
puts "Your two-factor authentication recovery codes are:\n\n" \
"#{codes}\n\n" \
"During sign in, use one of the codes above when prompted for\n" \
"your two-factor code. Then, visit your Profile Settings and add\n" \
"a new device so you do not lose access to your account again."
else
puts "An error occurred while trying to generate new recovery codes.\n" \
"#{resp['message']}"
end
end
def git_trace_available?
return false unless @config.git_trace_log_file
if Pathname(@config.git_trace_log_file).relative?
$logger.warn('git trace log path must be absolute, ignoring', git_trace_log_file: @config.git_trace_log_file)
return false
end
begin
File.open(@config.git_trace_log_file, 'a') { nil }
return true
rescue => ex
$logger.warn('Failed to open git trace log file', git_trace_log_file: @config.git_trace_log_file, error: ex.to_s)
return false
end
end
def repo_path=(repo_path)
raise ArgumentError, "Repository path not provided. Please make sure you're using GitLab v8.10 or later." unless repo_path
raise InvalidRepositoryPathError if File.absolute_path(repo_path) != repo_path
@repo_path = repo_path
end end
end end
require 'net/http'
require 'openssl'
require_relative 'gitlab_config'
require_relative 'httpunix'
module HTTPHelper module HTTPHelper
READ_TIMEOUT = 300 READ_TIMEOUT = 300
HTTP_SUCCESS = '200'.freeze
HTTP_MULTIPLE_CHOICES = '300'.freeze
HTTP_UNAUTHORIZED = '401'.freeze
HTTP_NOT_FOUND = '404'.freeze
HTTP_SUCCESS_LIKE = [HTTP_SUCCESS, HTTP_MULTIPLE_CHOICES].freeze
class ApiUnreachableError < StandardError; end
class NotFoundError < StandardError; end
protected protected
...@@ -91,7 +76,7 @@ module HTTPHelper ...@@ -91,7 +76,7 @@ module HTTPHelper
$logger.info('finished HTTP request', method: method.to_s.upcase, url: url, duration: Time.new - start_time) $logger.info('finished HTTP request', method: method.to_s.upcase, url: url, duration: Time.new - start_time)
end end
if HTTP_SUCCESS_LIKE.include?(response.code) if response.code == "200"
$logger.debug('Received response', code: response.code, body: response.body) $logger.debug('Received response', code: response.code, body: response.body)
else else
$logger.error('Call failed', method: method.to_s.upcase, url: url, code: response.code, body: response.body) $logger.error('Call failed', method: method.to_s.upcase, url: url, code: response.code, body: response.body)
...@@ -124,7 +109,7 @@ module HTTPHelper ...@@ -124,7 +109,7 @@ module HTTPHelper
end end
def secret_token def secret_token
@secret_token ||= File.read(config.secret_file) @secret_token ||= File.read config.secret_file
end end
def read_timeout def read_timeout
......
require_relative '../spec_helper'
require_relative '../../lib/action/api_2fa_recovery'
describe Action::API2FARecovery do
let(:key_id) { '1' }
let(:actor) { Actor::Key.new(key_id) }
let(:username) { 'testuser' }
let(:discover_payload) { { 'username' => username } }
let(:api) { double(GitlabNet) }
before do
allow(GitlabNet).to receive(:new).and_return(api)
allow(api).to receive(:discover).with(actor).and_return(discover_payload)
end
subject do
described_class.new(actor)
end
describe '#execute' do
context 'with an invalid repsonse' do
it 'returns nil' do
expect($stdin).to receive(:gets).and_return("meh\n")
expect do
expect(subject.execute(nil, nil)).to be_nil
end.to output(/New recovery codes have \*not\* been generated/).to_stdout
end
end
context 'with a negative response' do
before do
expect(subject).to receive(:continue?).and_return(false)
end
it 'returns nil' do
expect do
expect(subject.execute(nil, nil)).to be_nil
end.to output(/New recovery codes have \*not\* been generated/).to_stdout
end
end
context 'with an affirmative response' do
let(:recovery_codes) { %w{ 8dfe0f433208f40b289904c6072e4a72 c33cee7fd0a73edb56e61b785e49af03 } }
before do
expect(subject).to receive(:continue?).and_return(true)
expect(api).to receive(:two_factor_recovery_codes).with(actor).and_return(response)
end
context 'with a unsuccessful response' do
let(:response) { { 'success' => false } }
it 'puts error message to stdout' do
expect do
expect(subject.execute(nil, nil)).to be_falsey
end.to output(/An error occurred while trying to generate new recovery codes/).to_stdout
end
end
context 'with a successful response' do
let(:response) { { 'success' => true, 'recovery_codes' => recovery_codes } }
it 'puts information message including recovery codes to stdout' do
expect do
expect(subject.execute(nil, nil)).to be_truthy
end.to output(Regexp.new(recovery_codes.join("\n"))).to_stdout
end
end
end
end
end
require_relative '../spec_helper'
require_relative '../../lib/action/base'
describe Action::Base do
describe '.create_from_json' do
it 'raises a NotImplementedError exeption' do
expect do
described_class.create_from_json('nomatter')
end.to raise_error(NotImplementedError)
end
end
end
require_relative '../spec_helper'
require_relative '../../lib/action/git_lfs_authenticate'
describe Action::GitLFSAuthenticate do
let(:key_id) { '1' }
let(:repo_name) { 'gitlab-ci.git' }
let(:actor) { Actor::Key.new(key_id) }
let(:username) { 'testuser' }
let(:discover_payload) { { 'username' => username } }
let(:api) { double(GitlabNet) }
before do
allow(GitlabNet).to receive(:new).and_return(api)
allow(api).to receive(:discover).with(actor).and_return(discover_payload)
end
subject do
described_class.new(actor, repo_name)
end
describe '#execute' do
context 'when response from API is not a success' do
before do
expect(api).to receive(:lfs_authenticate).with(actor, repo_name).and_return(nil)
end
it 'returns nil' do
expect(subject.execute(nil, nil)).to be_nil
end
end
context 'when response from API is a success' do
let(:username) { 'testuser' }
let(:lfs_token) { '1234' }
let(:repository_http_path) { "/tmp/#{repo_name}" }
let(:gitlab_lfs_authentication) { GitlabLfsAuthentication.new(username, lfs_token, repository_http_path) }
before do
expect(api).to receive(:lfs_authenticate).with(actor, repo_name).and_return(gitlab_lfs_authentication)
end
it 'puts payload to stdout' do
expect($stdout).to receive(:puts).with('{"header":{"Authorization":"Basic dGVzdHVzZXI6MTIzNA=="},"href":"/tmp/gitlab-ci.git/info/lfs/"}')
expect(subject.execute(nil, nil)).to be_truthy
end
end
end
end
require_relative '../spec_helper'
require_relative '../../lib/action/gitaly'
describe Action::Gitaly do
let(:git_trace_log_file_valid) { '/tmp/git_trace_performance.log' }
let(:git_trace_log_file_invalid) { "/bleep-bop#{git_trace_log_file_valid}" }
let(:git_trace_log_file_relative) { "..#{git_trace_log_file_valid}" }
let(:key_id) { '1' }
let(:key_str) { 'key-1' }
let(:key) { Actor::Key.new(key_id) }
let(:gl_repository) { 'project-1' }
let(:gl_username) { 'testuser' }
let(:git_protocol) { 'version=2' }
let(:tmp_repos_path) { File.join(ROOT_PATH, 'tmp', 'repositories') }
let(:repo_name) { 'gitlab-ci.git' }
let(:repository_path) { File.join(tmp_repos_path, repo_name) }
let(:gitaly_address) { 'unix:gitaly.socket' }
let(:gitaly_token) { '123456' }
let(:gitaly) do
{
'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default' },
'address' => gitaly_address,
'token' => gitaly_token
}
end
describe '.create_from_json' do
it 'returns an instance of Action::Gitaly' do
json = {
"gl_repository" => gl_repository,
"gl_username" => gl_username,
"repository_path" => repository_path,
"gitaly" => gitaly
}
expect(described_class.create_from_json(key_id, json)).to be_instance_of(Action::Gitaly)
end
end
subject do
described_class.new(key, gl_repository, gl_username, git_protocol, repository_path, gitaly)
end
describe '#execute' do
let(:args) { [ repository_path ] }
let(:base_exec_env) do
{
'HOME' => ENV['HOME'],
'PATH' => ENV['PATH'],
'LD_LIBRARY_PATH' => ENV['LD_LIBRARY_PATH'],
'LANG' => ENV['LANG'],
'GL_ID' => key_str,
'GL_PROTOCOL' => GitlabNet::GL_PROTOCOL,
'GL_REPOSITORY' => gl_repository,
'GL_USERNAME' => gl_username,
'GITALY_TOKEN' => gitaly_token,
}
end
let(:with_trace_exec_env) do
base_exec_env.merge({
'GIT_TRACE' => git_trace_log_file,
'GIT_TRACE_PACKET' => git_trace_log_file,
'GIT_TRACE_PERFORMANCE' => git_trace_log_file
})
end
let(:gitaly_request) do
{
'repository' => gitaly['repository'],
'gl_repository' => gl_repository,
'gl_id' => key_str,
'gl_username' => gl_username,
'git_protocol' => git_protocol
}
end
context 'for migrated commands' do
context 'such as git-upload-pack' do
let(:git_trace_log_file) { nil }
let(:command) { 'git-upload-pack' }
before do
allow_any_instance_of(GitlabConfig).to receive(:git_trace_log_file).and_return(git_trace_log_file)
end
context 'with an invalid config.git_trace_log_file' do
let(:git_trace_log_file) { git_trace_log_file_invalid }
it 'returns true' do
expect(Kernel).to receive(:exec).with(
base_exec_env,
described_class::MIGRATED_COMMANDS[command],
gitaly_address,
JSON.dump(gitaly_request),
unsetenv_others: true,
chdir: ROOT_PATH
).and_return(true)
expect(subject.execute(command, args)).to be_truthy
end
end
context 'with n relative config.git_trace_log_file' do
let(:git_trace_log_file) { git_trace_log_file_relative }
it 'returns true' do
expect(Kernel).to receive(:exec).with(
base_exec_env,
described_class::MIGRATED_COMMANDS[command],
gitaly_address,
JSON.dump(gitaly_request),
unsetenv_others: true,
chdir: ROOT_PATH
).and_return(true)
expect(subject.execute(command, args)).to be_truthy
end
end
context 'with a valid config.git_trace_log_file' do
let(:git_trace_log_file) { git_trace_log_file_valid }
it 'returns true' do
expect(Kernel).to receive(:exec).with(
with_trace_exec_env,
described_class::MIGRATED_COMMANDS[command],
gitaly_address,
JSON.dump(gitaly_request),
unsetenv_others: true,
chdir: ROOT_PATH
).and_return(true)
expect(subject.execute(command, args)).to be_truthy
end
end
end
end
end
end
require_relative '../spec_helper'
require_relative '../../lib/actor/base'
describe Actor::Base do
describe '.identifier_key' do
it 'raises a NotImplementedError exception' do
expect do
described_class.identifier_key
end.to raise_error(NotImplementedError)
end
end
describe '.identifier_prefix' do
it 'raises a NotImplementedError exception' do
expect do
described_class.identifier_prefix
end.to raise_error(NotImplementedError)
end
end
describe '.id_regex' do
it 'raises a NotImplementedError exception' do
expect do
described_class.id_regex
end.to raise_error(NotImplementedError)
end
end
end
require_relative '../spec_helper'
require_relative '../../lib/actor/key'
describe Actor::Key do
let(:key_id) { '1' }
let(:username) { 'testuser' }
let(:api) { double(GitlabNet) }
let(:discover_payload) { { 'username' => username } }
let(:audit_usernames) { nil }
before do
allow(GitlabNet).to receive(:new).and_return(api)
allow(api).to receive(:discover).with(subject).and_return(discover_payload)
end
describe '.from' do
it 'returns an instance of Actor::Key' do
expect(described_class.from('key-1')).to be_a(Actor::Key)
end
it 'has a key_id == 1' do
expect(described_class.from('key-1').key_id).to eq '1'
end
end
describe '.identifier_prefix' do
it "returns 'key'" do
expect(described_class.identifier_prefix).to eql 'key'
end
end
describe '.identifier_key' do
it "returns 'key_id'" do
expect(described_class.identifier_key).to eql 'key_id'
end
end
subject { described_class.new(key_id, audit_usernames: audit_usernames) }
describe '#username' do
context 'with a valid user' do
it "returns '@testuser'" do
expect(subject.username).to eql '@testuser'
end
end
context 'without a valid user' do
let(:discover_payload) { nil }
it "returns 'Anonymous'" do
expect(subject.username).to eql 'Anonymous'
end
end
end
describe '#identifier' do
it "returns 'key-1'" do
expect(subject.identifier).to eql 'key-1'
end
end
describe '#log_username' do
context 'when audit_usernames is true' do
let(:audit_usernames) { true }
it "returns 'testuser'" do
expect(subject.log_username).to eql '@testuser'
end
end
context 'when audit_usernames is false' do
let(:audit_usernames) { false }
it "returns 'key with identifier key-1'" do
expect(subject.log_username).to eql 'key with identifier key-1'
end
end
end
end
require_relative '../spec_helper'
require_relative '../../lib/actor/user'
describe Actor::User do
let(:user_id) { '1' }
let(:username) { 'testuser' }
let(:audit_usernames) { nil }
describe '.from' do
it 'returns an instance of Actor::User' do
expect(described_class.from('user-1')).to be_a(Actor::User)
end
it 'has an id == 1' do
expect(described_class.from('user-1').id).to eq '1'
end
end
describe '.identifier_prefix' do
it "returns 'user'" do
expect(described_class.identifier_prefix).to eql 'user'
end
end
describe '.identifier_key' do
it "returns 'user_id'" do
expect(described_class.identifier_key).to eql 'user_id'
end
end
subject { described_class.new(user_id, audit_usernames: audit_usernames) }
describe '#username' do
it "returns 'user-1'" do
expect(subject.username).to eql 'user-1'
end
end
describe '#identifier' do
it "returns 'user-1'" do
expect(subject.identifier).to eql 'user-1'
end
end
describe '#log_username' do
context 'when audit_usernames is true' do
let(:audit_usernames) { true }
it "returns 'user-1'" do
expect(subject.log_username).to eql 'user-1'
end
end
context 'when audit_usernames is false' do
let(:audit_usernames) { false }
it "returns 'user with identifier user-1'" do
expect(subject.log_username).to eql 'user with identifier user-1'
end
end
end
end
require_relative '../spec_helper'
require_relative '../../lib/actor/username'
describe Actor::Username do
let(:username) { 'testuser' }
let(:api) { double(GitlabNet) }
let(:discover_payload) { { 'username' => username } }
let(:audit_usernames) { nil }
before do
allow(GitlabNet).to receive(:new).and_return(api)
allow(api).to receive(:discover).with(subject).and_return(discover_payload)
end
describe '.from' do
it 'returns an instance of Actor::Username' do
expect(described_class.from("username-#{username}")).to be_a(Actor::Username)
end
it 'has an id == 1' do
expect(described_class.from('username-1').id).to eq '1'
end
end
describe '.identifier_prefix' do
it "returns 'user'" do
expect(described_class.identifier_prefix).to eql 'username'
end
end
describe '.identifier_key' do
it "returns 'username'" do
expect(described_class.identifier_key).to eql 'username'
end
end
subject { described_class.new(username, audit_usernames: audit_usernames) }
describe '#username' do
context 'without a valid user' do
it "returns '@testuser'" do
expect(subject.username).to eql "@#{username}"
end
end
context 'without a valid user' do
let(:discover_payload) { nil }
it "returns 'Anonymous'" do
expect(subject.username).to eql 'Anonymous'
end
end
end
describe '#identifier' do
it "returns 'username-testuser'" do
expect(subject.identifier).to eql 'username-testuser'
end
end
describe '#log_username' do
context 'when audit_usernames is true' do
let(:audit_usernames) { true }
it "returns '@testuser'" do
expect(subject.log_username).to eql "@#{username}"
end
end
context 'when audit_usernames is false' do
let(:audit_usernames) { false }
it "returns 'user with identifier username-testuser'" do
expect(subject.log_username).to eql "user with identifier username-#{username}"
end
end
end
end
require_relative 'spec_helper'
require_relative '../lib/actor'
describe Actor do
describe '.new_from' do
context 'for an unsupported Actor type' do
it 'raises a NotImplementedError exception' do
expect do
described_class.new_from('unsupported-1')
end.to raise_error(Actor::UnsupportedActorError)
end
end
context 'for a supported Actor type' do
context 'of Key' do
it 'returns an instance of Key' do
expect(described_class.new_from('key-1')).to be_a(Actor::Key)
end
end
context 'of User' do
it 'returns an instance of User' do
expect(described_class.new_from('user-1')).to be_a(Actor::User)
end
end
context 'of Username' do
it 'returns an instance of Username' do
expect(described_class.new_from('username-john1')).to be_a(Actor::Username)
end
end
end
end
end
...@@ -7,27 +7,31 @@ describe GitlabAccess do ...@@ -7,27 +7,31 @@ describe GitlabAccess do
let(:repo_path) { File.join(repository_path, repo_name) + ".git" } let(:repo_path) { File.join(repository_path, repo_name) + ".git" }
let(:api) do let(:api) do
double(GitlabNet).tap do |api| double(GitlabNet).tap do |api|
allow(api).to receive(:check_access).and_return( api.stub(check_access: GitAccessStatus.new(true,
Action::Gitaly.new( 'ok',
'key-1', gl_repository: 'project-1',
'project-1', gl_id: 'user-123',
'testuser', gl_username: 'testuser',
'version=2', repository_path: '/home/git/repositories',
'/home/git/repositories', gitaly: nil,
nil git_protocol: 'version=2'))
)
)
end end
end end
subject do subject do
GitlabAccess.new(nil, repo_path, 'key-123', 'wow', 'ssh').tap do |access| GitlabAccess.new(nil, repo_path, 'key-123', 'wow', 'ssh').tap do |access|
allow(access).to receive(:exec_cmd).and_return(:exec_called) access.stub(exec_cmd: :exec_called)
allow(access).to receive(:api).and_return(api) access.stub(api: api)
end end
end end
before do before do
allow_any_instance_of(GitlabConfig).to receive(:repos_path).and_return(repository_path) GitlabConfig.any_instance.stub(repos_path: repository_path)
end
describe :initialize do
it { subject.repo_path.should == repo_path }
it { subject.changes.should == ['wow'] }
it { subject.protocol.should == 'ssh' }
end end
describe "#exec" do describe "#exec" do
...@@ -39,7 +43,16 @@ describe GitlabAccess do ...@@ -39,7 +43,16 @@ describe GitlabAccess do
context "access is denied" do context "access is denied" do
before do before do
allow(api).to receive(:check_access).and_raise(AccessDeniedError) api.stub(check_access: GitAccessStatus.new(
false,
'denied',
gl_repository: nil,
gl_id: nil,
gl_username: nil,
repository_path: nil,
gitaly: nil,
git_protocol: nil
))
end end
it "returns false" do it "returns false" do
...@@ -49,7 +62,7 @@ describe GitlabAccess do ...@@ -49,7 +62,7 @@ describe GitlabAccess do
context "API connection fails" do context "API connection fails" do
before do before do
allow(api).to receive(:check_access).and_raise(GitlabNet::ApiUnreachableError) api.stub(:check_access).and_raise(GitlabNet::ApiUnreachableError)
end end
it "returns false" do it "returns false" do
......
...@@ -3,55 +3,29 @@ require_relative '../lib/gitlab_config' ...@@ -3,55 +3,29 @@ require_relative '../lib/gitlab_config'
describe GitlabConfig do describe GitlabConfig do
let(:config) { GitlabConfig.new } let(:config) { GitlabConfig.new }
let(:config_data) do
{
# 'user' => 'git',
# 'http_settings' => {
# 'self_signed_cert' => false
# },
# 'log_level' => 'ERROR',
# 'audit_usernames' => true,
# 'log_format' => 'json', # Not sure on other values?
# 'git_trace_log_file' => nil
}
end
before do
expect(YAML).to receive(:load_file).and_return(config_data)
end
describe '#gitlab_url' do describe :gitlab_url do
let(:url) { 'http://test.com' } let(:url) { 'http://test.com' }
subject { config.gitlab_url } subject { config.gitlab_url }
before { config.send(:config)['gitlab_url'] = url }
before { config_data['gitlab_url'] = url }
it { should_not be_empty } it { should_not be_empty }
it { should eq(url) } it { should eq(url) }
context 'remove trailing slashes' do context 'remove trailing slashes' do
before { config_data['gitlab_url'] = url + '//' } before { config.send(:config)['gitlab_url'] = url + '//' }
it { should eq(url) } it { should eq(url) }
end end
end end
describe '#audit_usernames' do describe :audit_usernames do
subject { config.audit_usernames } subject { config.audit_usernames }
it("returns false by default") { should eq(false) } it("returns false by default") { should eq(false) }
end end
describe '#log_level' do describe :log_format do
subject { config.log_level }
it 'returns "INFO" by default' do
should eq('INFO')
end
end
describe '#log_format' do
subject { config.log_format } subject { config.log_format }
it 'returns "text" by default' do it 'returns "text" by default' do
......
...@@ -65,9 +65,9 @@ describe GitlabKeys do ...@@ -65,9 +65,9 @@ describe GitlabKeys do
describe :initialize do describe :initialize do
let(:gitlab_keys) { build_gitlab_keys('add-key', 'key-741', 'ssh-rsa AAAAB3NzaDAxx2E') } let(:gitlab_keys) { build_gitlab_keys('add-key', 'key-741', 'ssh-rsa AAAAB3NzaDAxx2E') }
it { expect(gitlab_keys.key).to eql 'ssh-rsa AAAAB3NzaDAxx2E' } it { gitlab_keys.key.should == 'ssh-rsa AAAAB3NzaDAxx2E' }
it { expect(gitlab_keys.instance_variable_get(:@command)).to eql 'add-key' } it { gitlab_keys.instance_variable_get(:@command).should == 'add-key' }
it { expect(gitlab_keys.instance_variable_get(:@key_id)).to eql 'key-741' } it { gitlab_keys.instance_variable_get(:@key_id).should == 'key-741' }
end end
describe :add_key do describe :add_key do
...@@ -77,7 +77,7 @@ describe GitlabKeys do ...@@ -77,7 +77,7 @@ describe GitlabKeys do
create_authorized_keys_fixture create_authorized_keys_fixture
gitlab_keys.send :add_key gitlab_keys.send :add_key
auth_line = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E" auth_line = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E"
expect(File.read(tmp_authorized_keys_path)).to eql "existing content\n#{auth_line}\n" File.read(tmp_authorized_keys_path).should == "existing content\n#{auth_line}\n"
end end
context "without file writing" do context "without file writing" do
...@@ -85,12 +85,12 @@ describe GitlabKeys do ...@@ -85,12 +85,12 @@ describe GitlabKeys do
before { create_authorized_keys_fixture } before { create_authorized_keys_fixture }
it "should log an add-key event" do it "should log an add-key event" do
expect($logger).to receive(:info).with("Adding key", {:key_id=>"key-741", :public_key=>"ssh-rsa AAAAB3NzaDAxx2E"}) $logger.should_receive(:info).with("Adding key", {:key_id=>"key-741", :public_key=>"ssh-rsa AAAAB3NzaDAxx2E"})
gitlab_keys.send :add_key gitlab_keys.send :add_key
end end
it "should return true" do it "should return true" do
expect(gitlab_keys.send(:add_key)).to be_truthy gitlab_keys.send(:add_key).should be_truthy
end end
end end
end end
...@@ -104,7 +104,7 @@ describe GitlabKeys do ...@@ -104,7 +104,7 @@ describe GitlabKeys do
create_authorized_keys_fixture create_authorized_keys_fixture
gitlab_keys.send :add_key gitlab_keys.send :add_key
auth_line1 = 'key-741 AAAAB3NzaDAxx2E' auth_line1 = 'key-741 AAAAB3NzaDAxx2E'
expect(gitlab_keys.send(:list_keys)).to eql "#{auth_line1}\n" gitlab_keys.send(:list_keys).should == "#{auth_line1}\n"
end end
end end
...@@ -118,9 +118,10 @@ describe GitlabKeys do ...@@ -118,9 +118,10 @@ describe GitlabKeys do
end end
it 'outputs the key IDs, separated by newlines' do it 'outputs the key IDs, separated by newlines' do
expect do output = capture_stdout do
gitlab_keys.send(:list_key_ids) gitlab_keys.send(:list_key_ids)
end.to output("1\n2\n3\n9000\n").to_stdout end
output.should match "1\n2\n3\n9000"
end end
end end
...@@ -129,38 +130,38 @@ describe GitlabKeys do ...@@ -129,38 +130,38 @@ describe GitlabKeys do
let(:fake_stdin) { StringIO.new("key-12\tssh-dsa ASDFASGADG\nkey-123\tssh-rsa GFDGDFSGSDFG\n", 'r') } let(:fake_stdin) { StringIO.new("key-12\tssh-dsa ASDFASGADG\nkey-123\tssh-rsa GFDGDFSGSDFG\n", 'r') }
before do before do
create_authorized_keys_fixture create_authorized_keys_fixture
allow(gitlab_keys).to receive(:stdin).and_return(fake_stdin) gitlab_keys.stub(stdin: fake_stdin)
end end
it "adds lines at the end of the file" do it "adds lines at the end of the file" do
gitlab_keys.send :batch_add_keys gitlab_keys.send :batch_add_keys
auth_line1 = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-12\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dsa ASDFASGADG" auth_line1 = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-12\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dsa ASDFASGADG"
auth_line2 = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-123\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa GFDGDFSGSDFG" auth_line2 = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-123\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa GFDGDFSGSDFG"
expect(File.read(tmp_authorized_keys_path)).to eql "existing content\n#{auth_line1}\n#{auth_line2}\n" File.read(tmp_authorized_keys_path).should == "existing content\n#{auth_line1}\n#{auth_line2}\n"
end end
context "with invalid input" do context "with invalid input" do
let(:fake_stdin) { StringIO.new("key-12\tssh-dsa ASDFASGADG\nkey-123\tssh-rsa GFDGDFSGSDFG\nfoo\tbar\tbaz\n", 'r') } let(:fake_stdin) { StringIO.new("key-12\tssh-dsa ASDFASGADG\nkey-123\tssh-rsa GFDGDFSGSDFG\nfoo\tbar\tbaz\n", 'r') }
it "aborts" do it "aborts" do
expect(gitlab_keys).to receive(:abort) gitlab_keys.should_receive(:abort)
gitlab_keys.send :batch_add_keys gitlab_keys.send :batch_add_keys
end end
end end
context "without file writing" do context "without file writing" do
before do before do
expect(gitlab_keys).to receive(:open).and_yield(double(:file, puts: nil, chmod: nil)) gitlab_keys.should_receive(:open).and_yield(double(:file, puts: nil, chmod: nil))
end end
it "should log an add-key event" do it "should log an add-key event" do
expect($logger).to receive(:info).with("Adding key", key_id: 'key-12', public_key: "ssh-dsa ASDFASGADG") $logger.should_receive(:info).with("Adding key", key_id: 'key-12', public_key: "ssh-dsa ASDFASGADG")
expect($logger).to receive(:info).with("Adding key", key_id: 'key-123', public_key: "ssh-rsa GFDGDFSGSDFG") $logger.should_receive(:info).with("Adding key", key_id: 'key-123', public_key: "ssh-rsa GFDGDFSGSDFG")
gitlab_keys.send :batch_add_keys gitlab_keys.send :batch_add_keys
end end
it "should return true" do it "should return true" do
expect(gitlab_keys.send(:batch_add_keys)).to be_truthy gitlab_keys.send(:batch_add_keys).should be_truthy
end end
end end
end end
...@@ -186,22 +187,22 @@ describe GitlabKeys do ...@@ -186,22 +187,22 @@ describe GitlabKeys do
end end
gitlab_keys.send :rm_key gitlab_keys.send :rm_key
erased_line = delete_line.gsub(/./, '#') erased_line = delete_line.gsub(/./, '#')
expect(File.read(tmp_authorized_keys_path)).to eql "existing content\n#{erased_line}\n#{other_line}\n" File.read(tmp_authorized_keys_path).should == "existing content\n#{erased_line}\n#{other_line}\n"
end end
context "without file writing" do context "without file writing" do
before do before do
allow(gitlab_keys).to receive(:open) gitlab_keys.stub(:open)
allow(gitlab_keys).to receive(:lock).and_yield gitlab_keys.stub(:lock).and_yield
end end
it "should log an rm-key event" do it "should log an rm-key event" do
expect($logger).to receive(:info).with("Removing key", key_id: "key-741") $logger.should_receive(:info).with("Removing key", key_id: "key-741")
gitlab_keys.send :rm_key gitlab_keys.send :rm_key
end end
it "should return true" do it "should return true" do
expect(gitlab_keys.send(:rm_key)).to be_truthy gitlab_keys.send(:rm_key).should be_truthy
end end
end end
...@@ -218,7 +219,7 @@ describe GitlabKeys do ...@@ -218,7 +219,7 @@ describe GitlabKeys do
end end
gitlab_keys.send :rm_key gitlab_keys.send :rm_key
erased_line = delete_line.gsub(/./, '#') erased_line = delete_line.gsub(/./, '#')
expect(File.read(tmp_authorized_keys_path)).to eql "existing content\n#{erased_line}\n#{other_line}\n" File.read(tmp_authorized_keys_path).should == "existing content\n#{erased_line}\n#{other_line}\n"
end end
end end
end end
...@@ -227,8 +228,8 @@ describe GitlabKeys do ...@@ -227,8 +228,8 @@ describe GitlabKeys do
let(:gitlab_keys) { build_gitlab_keys('clear') } let(:gitlab_keys) { build_gitlab_keys('clear') }
it "should return true" do it "should return true" do
allow(gitlab_keys).to receive(:open) gitlab_keys.stub(:open)
expect(gitlab_keys.send(:clear)).to be_truthy gitlab_keys.send(:clear).should be_truthy
end end
end end
...@@ -241,7 +242,7 @@ describe GitlabKeys do ...@@ -241,7 +242,7 @@ describe GitlabKeys do
end end
it 'returns false if opening raises an exception' do it 'returns false if opening raises an exception' do
expect(gitlab_keys).to receive(:open_auth_file).and_raise("imaginary error") gitlab_keys.should_receive(:open_auth_file).and_raise("imaginary error")
expect(gitlab_keys.exec).to eq(false) expect(gitlab_keys.exec).to eq(false)
end end
...@@ -256,51 +257,51 @@ describe GitlabKeys do ...@@ -256,51 +257,51 @@ describe GitlabKeys do
describe :exec do describe :exec do
it 'add-key arg should execute add_key method' do it 'add-key arg should execute add_key method' do
gitlab_keys = build_gitlab_keys('add-key') gitlab_keys = build_gitlab_keys('add-key')
expect(gitlab_keys).to receive(:add_key) gitlab_keys.should_receive(:add_key)
gitlab_keys.exec gitlab_keys.exec
end end
it 'batch-add-keys arg should execute batch_add_keys method' do it 'batch-add-keys arg should execute batch_add_keys method' do
gitlab_keys = build_gitlab_keys('batch-add-keys') gitlab_keys = build_gitlab_keys('batch-add-keys')
expect(gitlab_keys).to receive(:batch_add_keys) gitlab_keys.should_receive(:batch_add_keys)
gitlab_keys.exec gitlab_keys.exec
end end
it 'rm-key arg should execute rm_key method' do it 'rm-key arg should execute rm_key method' do
gitlab_keys = build_gitlab_keys('rm-key') gitlab_keys = build_gitlab_keys('rm-key')
expect(gitlab_keys).to receive(:rm_key) gitlab_keys.should_receive(:rm_key)
gitlab_keys.exec gitlab_keys.exec
end end
it 'clear arg should execute clear method' do it 'clear arg should execute clear method' do
gitlab_keys = build_gitlab_keys('clear') gitlab_keys = build_gitlab_keys('clear')
expect(gitlab_keys).to receive(:clear) gitlab_keys.should_receive(:clear)
gitlab_keys.exec gitlab_keys.exec
end end
it 'check-permissions arg should execute check_permissions method' do it 'check-permissions arg should execute check_permissions method' do
gitlab_keys = build_gitlab_keys('check-permissions') gitlab_keys = build_gitlab_keys('check-permissions')
expect(gitlab_keys).to receive(:check_permissions) gitlab_keys.should_receive(:check_permissions)
gitlab_keys.exec gitlab_keys.exec
end end
it 'should puts message if unknown command arg' do it 'should puts message if unknown command arg' do
gitlab_keys = build_gitlab_keys('change-key') gitlab_keys = build_gitlab_keys('change-key')
expect(gitlab_keys).to receive(:puts).with('not allowed') gitlab_keys.should_receive(:puts).with('not allowed')
gitlab_keys.exec gitlab_keys.exec
end end
it 'should log a warning on unknown commands' do it 'should log a warning on unknown commands' do
gitlab_keys = build_gitlab_keys('nooope') gitlab_keys = build_gitlab_keys('nooope')
allow(gitlab_keys).to receive(:puts).and_return(nil) gitlab_keys.stub(puts: nil)
expect($logger).to receive(:warn).with("Attempt to execute invalid gitlab-keys command", command: '"nooope"') $logger.should_receive(:warn).with("Attempt to execute invalid gitlab-keys command", command: '"nooope"')
gitlab_keys.exec gitlab_keys.exec
end end
end end
describe :lock do describe :lock do
before do before do
allow_any_instance_of(GitlabKeys).to receive(:lock_file).and_return(tmp_lock_file_path) GitlabKeys.any_instance.stub(lock_file: tmp_lock_file_path)
end end
it "should raise exception if operation lasts more then timeout" do it "should raise exception if operation lasts more then timeout" do
...@@ -309,7 +310,7 @@ describe GitlabKeys do ...@@ -309,7 +310,7 @@ describe GitlabKeys do
key.send :lock, 1 do key.send :lock, 1 do
sleep 2 sleep 2
end end
end.to raise_error(Timeout::Error) end.to raise_error
end end
it "should actually lock file" do it "should actually lock file" do
...@@ -334,7 +335,7 @@ describe GitlabKeys do ...@@ -334,7 +335,7 @@ describe GitlabKeys do
end end
thr1.join thr1.join
expect($global).to eql "foobar" $global.should == "foobar"
end end
end end
...@@ -352,7 +353,7 @@ describe GitlabKeys do ...@@ -352,7 +353,7 @@ describe GitlabKeys do
def create_authorized_keys_fixture(existing_content: 'existing content') def create_authorized_keys_fixture(existing_content: 'existing content')
FileUtils.mkdir_p(File.dirname(tmp_authorized_keys_path)) FileUtils.mkdir_p(File.dirname(tmp_authorized_keys_path))
open(tmp_authorized_keys_path, 'w') { |file| file.puts(existing_content) } open(tmp_authorized_keys_path, 'w') { |file| file.puts(existing_content) }
allow(gitlab_keys).to receive(:auth_file).and_return(tmp_authorized_keys_path) gitlab_keys.stub(auth_file: tmp_authorized_keys_path)
end end
def tmp_authorized_keys_path def tmp_authorized_keys_path
......
...@@ -16,22 +16,22 @@ describe GitlabLfsAuthentication do ...@@ -16,22 +16,22 @@ describe GitlabLfsAuthentication do
end end
describe '#build_from_json' do describe '#build_from_json' do
it { expect(subject.username).to eql 'dzaporozhets' } it { subject.username.should == 'dzaporozhets' }
it { expect(subject.lfs_token).to eql 'wsnys8Zm8Jn7zyhHTAAK' } it { subject.lfs_token.should == 'wsnys8Zm8Jn7zyhHTAAK' }
it { expect(subject.repository_http_path).to eql 'http://gitlab.dev/repo' } it { subject.repository_http_path.should == 'http://gitlab.dev/repo' }
end end
describe '#authentication_payload' do describe '#authentication_payload' do
result = "{\"header\":{\"Authorization\":\"Basic ZHphcG9yb3poZXRzOndzbnlzOFptOEpuN3p5aEhUQUFL\"},\"href\":\"http://gitlab.dev/repo/info/lfs/\"}" result = "{\"header\":{\"Authorization\":\"Basic ZHphcG9yb3poZXRzOndzbnlzOFptOEpuN3p5aEhUQUFL\"},\"href\":\"http://gitlab.dev/repo/info/lfs/\"}"
it { expect(subject.authentication_payload).to eq(result) } it { subject.authentication_payload.should eq(result) }
it 'should be a proper JSON' do it 'should be a proper JSON' do
payload = subject.authentication_payload payload = subject.authentication_payload
json_payload = JSON.parse(payload) json_payload = JSON.parse(payload)
expect(json_payload['header']['Authorization']).to eq('Basic ZHphcG9yb3poZXRzOndzbnlzOFptOEpuN3p5aEhUQUFL') json_payload['header']['Authorization'].should eq('Basic ZHphcG9yb3poZXRzOndzbnlzOFptOEpuN3p5aEhUQUFL')
expect(json_payload['href']).to eq('http://gitlab.dev/repo/info/lfs/') json_payload['href'].should eq('http://gitlab.dev/repo/info/lfs/')
end end
end end
end end
...@@ -6,7 +6,7 @@ describe :convert_log_level do ...@@ -6,7 +6,7 @@ describe :convert_log_level do
subject { convert_log_level :extreme } subject { convert_log_level :extreme }
it "converts invalid log level to Logger::INFO" do it "converts invalid log level to Logger::INFO" do
expect($stderr).to receive(:puts).at_least(:once) $stderr.should_receive(:puts).at_least(:once)
should eq(Logger::INFO) should eq(Logger::INFO)
end end
end end
......
require_relative 'spec_helper' require_relative 'spec_helper'
require_relative '../lib/gitlab_net' require_relative '../lib/gitlab_net'
require_relative '../lib/gitlab_access_status'
describe GitlabNet, vcr: true do describe GitlabNet, vcr: true do
let(:gitlab_net) { described_class.new } let(:gitlab_net) { described_class.new }
...@@ -7,66 +8,57 @@ describe GitlabNet, vcr: true do ...@@ -7,66 +8,57 @@ describe GitlabNet, vcr: true do
let(:base_api_endpoint) { 'http://localhost:3000/api/v4' } let(:base_api_endpoint) { 'http://localhost:3000/api/v4' }
let(:internal_api_endpoint) { 'http://localhost:3000/api/v4/internal' } let(:internal_api_endpoint) { 'http://localhost:3000/api/v4/internal' }
let(:project) { 'gitlab-org/gitlab-test.git' } let(:project) { 'gitlab-org/gitlab-test.git' }
let(:key) { 'key-1' }
let(:actor1) { Actor.new_from('key-1') } let(:key2) { 'key-2' }
let(:bad_actor1) { Actor.new_from('key-777') }
let(:actor2) { Actor.new_from('user-1') }
let(:secret) { "0a3938d9d95d807e94d937af3a4fbbea\n" } let(:secret) { "0a3938d9d95d807e94d937af3a4fbbea\n" }
before do before do
$logger = double('logger').as_null_object $logger = double('logger').as_null_object
allow(gitlab_net).to receive(:base_api_endpoint).and_return(base_api_endpoint) gitlab_net.stub(:base_api_endpoint).and_return(base_api_endpoint)
allow(gitlab_net).to receive(:secret_token).and_return(secret) gitlab_net.stub(:secret_token).and_return(secret)
end end
describe '#check' do describe '#check' do
it 'should return 200 code for gitlab check' do it 'should return 200 code for gitlab check' do
VCR.use_cassette("check-ok") do VCR.use_cassette("check-ok") do
result = gitlab_net.check result = gitlab_net.check
expect(result.code).to eql('200') result.code.should == '200'
end end
end end
it 'adds the secret_token to request' do it 'adds the secret_token to request' do
VCR.use_cassette("check-ok") do VCR.use_cassette("check-ok") do
allow_any_instance_of(Net::HTTP::Get).to receive(:set_form_data).with(hash_including(secret_token: secret)) Net::HTTP::Get.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: secret))
gitlab_net.check gitlab_net.check
end end
end end
it "raises an exception if the connection fails" do it "raises an exception if the connection fails" do
allow_any_instance_of(Net::HTTP).to receive(:request).and_raise(StandardError) Net::HTTP.any_instance.stub(:request).and_raise(StandardError)
expect do gitlab_net.check end.to raise_error(GitlabNet::ApiUnreachableError) expect { gitlab_net.check }.to raise_error(GitlabNet::ApiUnreachableError)
end end
end end
describe '#discover' do describe '#discover' do
it 'returns user has based on key id' do it 'should return user has based on key id' do
VCR.use_cassette("discover-ok") do VCR.use_cassette("discover-ok") do
user = gitlab_net.discover(actor1) user = gitlab_net.discover(key)
expect(user['name']).to eql 'Administrator' user['name'].should == 'Administrator'
expect(user['username']).to eql 'root' user['username'].should == 'root'
end
end
it 'returns nil if the user cannot be found' do
VCR.use_cassette("discover-not-found") do
expect(gitlab_net.discover(actor1)).to be_nil
end end
end end
it 'adds the secret_token to request' do it 'adds the secret_token to request' do
VCR.use_cassette("discover-ok") do VCR.use_cassette("discover-ok") do
allow_any_instance_of(Net::HTTP::Get).to receive(:set_form_data).with(hash_including(secret_token: secret)) Net::HTTP::Get.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: secret))
gitlab_net.discover(actor1) gitlab_net.discover(key)
end end
end end
it "raises an exception if the connection fails" do it "raises an exception if the connection fails" do
VCR.use_cassette("discover-ok") do VCR.use_cassette("discover-ok") do
allow_any_instance_of(Net::HTTP).to receive(:request).and_raise(StandardError) Net::HTTP.any_instance.stub(:request).and_raise(StandardError)
expect(gitlab_net.discover(actor1)).to be_nil expect { gitlab_net.discover(key) }.to raise_error(GitlabNet::ApiUnreachableError)
end end
end end
end end
...@@ -75,10 +67,10 @@ describe GitlabNet, vcr: true do ...@@ -75,10 +67,10 @@ describe GitlabNet, vcr: true do
context 'lfs authentication succeeded' do context 'lfs authentication succeeded' do
it 'should return the correct data' do it 'should return the correct data' do
VCR.use_cassette('lfs-authenticate-ok') do VCR.use_cassette('lfs-authenticate-ok') do
lfs_access = gitlab_net.lfs_authenticate(actor1, project) lfs_access = gitlab_net.lfs_authenticate(key, project)
expect(lfs_access.username).to eql 'root' lfs_access.username.should == 'root'
expect(lfs_access.lfs_token).to eql 'Hyzhyde_wLUeyUQsR3tHGTG8eNocVQm4ssioTEsBSdb6KwCSzQ' lfs_access.lfs_token.should == 'Hyzhyde_wLUeyUQsR3tHGTG8eNocVQm4ssioTEsBSdb6KwCSzQ'
expect(lfs_access.repository_http_path).to eql URI.join(internal_api_endpoint.sub('api/v4', ''), project).to_s lfs_access.repository_http_path.should == URI.join(internal_api_endpoint.sub('api/v4', ''), project).to_s
end end
end end
end end
...@@ -89,7 +81,7 @@ describe GitlabNet, vcr: true do ...@@ -89,7 +81,7 @@ describe GitlabNet, vcr: true do
it 'should return message' do it 'should return message' do
VCR.use_cassette("broadcast_message-ok") do VCR.use_cassette("broadcast_message-ok") do
result = gitlab_net.broadcast_message result = gitlab_net.broadcast_message
expect(result["message"]).to eql "Message" result["message"].should == "Message"
end end
end end
end end
...@@ -98,7 +90,7 @@ describe GitlabNet, vcr: true do ...@@ -98,7 +90,7 @@ describe GitlabNet, vcr: true do
it 'should return nil' do it 'should return nil' do
VCR.use_cassette("broadcast_message-none") do VCR.use_cassette("broadcast_message-none") do
result = gitlab_net.broadcast_message result = gitlab_net.broadcast_message
expect(result).to eql({}) result.should == {}
end end
end end
end end
...@@ -110,13 +102,13 @@ describe GitlabNet, vcr: true do ...@@ -110,13 +102,13 @@ describe GitlabNet, vcr: true do
let(:encoded_changes) { "123456%20789012%20refs/heads/test%0A654321%20210987%20refs/tags/tag" } let(:encoded_changes) { "123456%20789012%20refs/heads/test%0A654321%20210987%20refs/tags/tag" }
it "sends the given arguments as encoded URL parameters" do it "sends the given arguments as encoded URL parameters" do
expect(gitlab_net).to receive(:get).with("#{internal_api_endpoint}/merge_request_urls?project=#{project}&changes=#{encoded_changes}&gl_repository=#{gl_repository}") gitlab_net.should_receive(:get).with("#{internal_api_endpoint}/merge_request_urls?project=#{project}&changes=#{encoded_changes}&gl_repository=#{gl_repository}")
gitlab_net.merge_request_urls(gl_repository, project, changes) gitlab_net.merge_request_urls(gl_repository, project, changes)
end end
it "omits the gl_repository parameter if it's nil" do it "omits the gl_repository parameter if it's nil" do
expect(gitlab_net).to receive(:get).with("#{internal_api_endpoint}/merge_request_urls?project=#{project}&changes=#{encoded_changes}") gitlab_net.should_receive(:get).with("#{internal_api_endpoint}/merge_request_urls?project=#{project}&changes=#{encoded_changes}")
gitlab_net.merge_request_urls(nil, project, changes) gitlab_net.merge_request_urls(nil, project, changes)
end end
...@@ -143,7 +135,8 @@ describe GitlabNet, vcr: true do ...@@ -143,7 +135,8 @@ describe GitlabNet, vcr: true do
subject { gitlab_net.pre_receive(gl_repository) } subject { gitlab_net.pre_receive(gl_repository) }
it 'sends the correct parameters and returns the request body parsed' do it 'sends the correct parameters and returns the request body parsed' do
allow_any_instance_of(Net::HTTP::Post).to receive(:set_form_data).with(hash_including(params)) Net::HTTP::Post.any_instance.should_receive(:set_form_data)
.with(hash_including(params))
VCR.use_cassette("pre-receive") { subject } VCR.use_cassette("pre-receive") { subject }
end end
...@@ -154,9 +147,9 @@ describe GitlabNet, vcr: true do ...@@ -154,9 +147,9 @@ describe GitlabNet, vcr: true do
end end
end end
it 'throws a NotFoundError when pre-receive is not available' do it 'throws a NotFound error when pre-receive is not available' do
VCR.use_cassette("pre-receive-not-found") do VCR.use_cassette("pre-receive-not-found") do
expect do subject end.to raise_error(GitlabNet::NotFoundError) expect { subject }.to raise_error(GitlabNet::NotFound)
end end
end end
end end
...@@ -165,7 +158,7 @@ describe GitlabNet, vcr: true do ...@@ -165,7 +158,7 @@ describe GitlabNet, vcr: true do
let(:gl_repository) { "project-1" } let(:gl_repository) { "project-1" }
let(:changes) { "123456 789012 refs/heads/test\n654321 210987 refs/tags/tag" } let(:changes) { "123456 789012 refs/heads/test\n654321 210987 refs/tags/tag" }
let(:params) do let(:params) do
{ gl_repository: gl_repository, identifier: actor1.identifier, changes: changes } { gl_repository: gl_repository, identifier: key, changes: changes }
end end
let(:merge_request_urls) do let(:merge_request_urls) do
[{ [{
...@@ -175,10 +168,11 @@ describe GitlabNet, vcr: true do ...@@ -175,10 +168,11 @@ describe GitlabNet, vcr: true do
}] }]
end end
subject { gitlab_net.post_receive(gl_repository, actor1, changes) } subject { gitlab_net.post_receive(gl_repository, key, changes) }
it 'sends the correct parameters' do it 'sends the correct parameters' do
allow_any_instance_of(Net::HTTP::Post).to receive(:set_form_data).with(hash_including(params)) Net::HTTP::Post.any_instance.should_receive(:set_form_data).with(hash_including(params))
VCR.use_cassette("post-receive") do VCR.use_cassette("post-receive") do
subject subject
...@@ -193,9 +187,9 @@ describe GitlabNet, vcr: true do ...@@ -193,9 +187,9 @@ describe GitlabNet, vcr: true do
end end
end end
it 'throws a NotFoundError when post-receive is not available' do it 'throws a NotFound error when post-receive is not available' do
VCR.use_cassette("post-receive-not-found") do VCR.use_cassette("post-receive-not-found") do
expect do subject end.to raise_error(GitlabNet::NotFoundError) expect { subject }.to raise_error(GitlabNet::NotFound)
end end
end end
end end
...@@ -206,21 +200,21 @@ describe GitlabNet, vcr: true do ...@@ -206,21 +200,21 @@ describe GitlabNet, vcr: true do
it "should return nil when the resource is not implemented" do it "should return nil when the resource is not implemented" do
VCR.use_cassette("ssh-key-not-implemented") do VCR.use_cassette("ssh-key-not-implemented") do
result = gitlab_net.authorized_key("whatever") result = gitlab_net.authorized_key("whatever")
expect(result).to be_nil result.should be_nil
end end
end end
it "should return nil when the fingerprint is not found" do it "should return nil when the fingerprint is not found" do
VCR.use_cassette("ssh-key-not-found") do VCR.use_cassette("ssh-key-not-found") do
result = gitlab_net.authorized_key("whatever") result = gitlab_net.authorized_key("whatever")
expect(result).to be_nil result.should be_nil
end end
end end
it "should return a ssh key with a valid fingerprint" do it "should return a ssh key with a valid fingerprint" do
VCR.use_cassette("ssh-key-ok") do VCR.use_cassette("ssh-key-ok") do
result = gitlab_net.authorized_key(ssh_key) result = gitlab_net.authorized_key(ssh_key)
expect(result).to eql({ result.should eq({
"can_push" => false, "can_push" => false,
"created_at" => "2017-06-21T09:50:07.150Z", "created_at" => "2017-06-21T09:50:07.150Z",
"id" => 99, "id" => 99,
...@@ -234,7 +228,7 @@ describe GitlabNet, vcr: true do ...@@ -234,7 +228,7 @@ describe GitlabNet, vcr: true do
describe '#two_factor_recovery_codes' do describe '#two_factor_recovery_codes' do
it 'returns two factor recovery codes' do it 'returns two factor recovery codes' do
VCR.use_cassette('two-factor-recovery-codes') do VCR.use_cassette('two-factor-recovery-codes') do
result = gitlab_net.two_factor_recovery_codes(actor1) result = gitlab_net.two_factor_recovery_codes(key)
expect(result['success']).to be_truthy expect(result['success']).to be_truthy
expect(result['recovery_codes']).to eq(['f67c514de60c4953','41278385fc00c1e0']) expect(result['recovery_codes']).to eq(['f67c514de60c4953','41278385fc00c1e0'])
end end
...@@ -242,7 +236,7 @@ describe GitlabNet, vcr: true do ...@@ -242,7 +236,7 @@ describe GitlabNet, vcr: true do
it 'returns false when recovery codes cannot be generated' do it 'returns false when recovery codes cannot be generated' do
VCR.use_cassette('two-factor-recovery-codes-fail') do VCR.use_cassette('two-factor-recovery-codes-fail') do
result = gitlab_net.two_factor_recovery_codes(bad_actor1) result = gitlab_net.two_factor_recovery_codes('key-777')
expect(result['success']).to be_falsey expect(result['success']).to be_falsey
expect(result['message']).to eq('Could not find the given key') expect(result['message']).to eq('Could not find the given key')
end end
...@@ -258,7 +252,7 @@ describe GitlabNet, vcr: true do ...@@ -258,7 +252,7 @@ describe GitlabNet, vcr: true do
it 'sets the arguments as form parameters' do it 'sets the arguments as form parameters' do
VCR.use_cassette('notify-post-receive') do VCR.use_cassette('notify-post-receive') do
allow_any_instance_of(Net::HTTP::Post).to receive(:set_form_data).with(hash_including(params)) Net::HTTP::Post.any_instance.should_receive(:set_form_data).with(hash_including(params))
gitlab_net.notify_post_receive(gl_repository, repo_path) gitlab_net.notify_post_receive(gl_repository, repo_path)
end end
end end
...@@ -271,159 +265,92 @@ describe GitlabNet, vcr: true do ...@@ -271,159 +265,92 @@ describe GitlabNet, vcr: true do
end end
describe '#check_access' do describe '#check_access' do
context 'something is wrong with the API response' do
context 'but response is JSON parsable' do
it 'raises an UnknownError exception' do
VCR.use_cassette('failed-push') do
expect do
gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'ssh')
end.to raise_error(UnknownError, 'API is not accessible: An internal server error occurred')
end
end
end
context 'but response is not JSON parsable' do
it 'raises an UnknownError exception' do
VCR.use_cassette('failed-push-unparsable') do
expect do
gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'ssh')
end.to raise_error(UnknownError, 'API is not accessible')
end
end
end
end
context 'ssh key with access nil, to project' do context 'ssh key with access nil, to project' do
it 'should allow push access for host' do it 'should allow pull access for host' do
VCR.use_cassette('allowed-push') do VCR.use_cassette("allowed-pull") do
action = gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'ssh') access = gitlab_net.check_access('git-receive-pack', nil, project, key, changes, 'ssh')
expect(action).to be_instance_of(Action::Gitaly) access.allowed?.should be_truthy
end end
end end
it 'adds the secret_token to the request' do it 'adds the secret_token to the request' do
VCR.use_cassette('allowed-pull') do VCR.use_cassette("allowed-pull") do
allow_any_instance_of(Net::HTTP::Post).to receive(:set_form_data).with(hash_including(secret_token: secret)) Net::HTTP::Post.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: secret))
gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'ssh') gitlab_net.check_access('git-receive-pack', nil, project, key, changes, 'ssh')
end end
end end
it 'should allow pull access for host' do it 'should allow push access for host' do
VCR.use_cassette("allowed-pull") do VCR.use_cassette("allowed-push") do
action = gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'ssh') access = gitlab_net.check_access('git-upload-pack', nil, project, key, changes, 'ssh')
expect(action).to be_instance_of(Action::Gitaly) access.allowed?.should be_truthy
end end
end end
end end
context 'ssh access has been disabled' do context 'ssh access has been disabled' do
it 'should deny pull access for host' do it 'should deny pull access for host' do
VCR.use_cassette('ssh-pull-disabled-old') do
expect do
gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'http')
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed')
end
VCR.use_cassette('ssh-pull-disabled') do VCR.use_cassette('ssh-pull-disabled') do
expect do access = gitlab_net.check_access('git-upload-pack', nil, project, key, changes, 'ssh')
gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'http') access.allowed?.should be_falsey
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed') access.message.should eq 'Git access over SSH is not allowed'
end end
end end
it 'should deny push access for host' do it 'should deny push access for host' do
VCR.use_cassette('ssh-push-disabled-old') do
expect do
gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'ssh')
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed')
end
VCR.use_cassette('ssh-push-disabled') do VCR.use_cassette('ssh-push-disabled') do
expect do access = gitlab_net.check_access('git-receive-pack', nil, project, key, changes, 'ssh')
gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'ssh') access.allowed?.should be_falsey
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed') access.message.should eq 'Git access over SSH is not allowed'
end end
end end
end end
context 'http access has been disabled' do context 'http access has been disabled' do
it 'should deny pull access for host' do it 'should deny pull access for host' do
VCR.use_cassette('http-pull-disabled-old') do
expect do
gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'http')
end.to raise_error(AccessDeniedError, 'Pulling over HTTP is not allowed.')
end
VCR.use_cassette('http-pull-disabled') do VCR.use_cassette('http-pull-disabled') do
expect do access = gitlab_net.check_access('git-upload-pack', nil, project, key, changes, 'http')
gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'http') access.allowed?.should be_falsey
end.to raise_error(AccessDeniedError, 'Pulling over HTTP is not allowed.') access.message.should eq 'Pulling over HTTP is not allowed.'
end end
end end
it 'should deny push access for host' do it 'should deny push access for host' do
VCR.use_cassette('http-push-disabled-old') do VCR.use_cassette("http-push-disabled") do
expect do access = gitlab_net.check_access('git-receive-pack', nil, project, key, changes, 'http')
gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'http') access.allowed?.should be_falsey
end.to raise_error(AccessDeniedError, 'Pushing over HTTP is not allowed.') access.message.should eq 'Pushing over HTTP is not allowed.'
end
VCR.use_cassette('http-push-disabled') do
expect do
gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'http')
end.to raise_error(AccessDeniedError, 'Pushing over HTTP is not allowed.')
end end
end end
end end
context 'ssh key without access to project' do context 'ssh key without access to project' do
it 'should deny pull access for host' do it 'should deny pull access for host' do
VCR.use_cassette('ssh-pull-project-denied-old') do VCR.use_cassette("ssh-pull-project-denied") do
expect do access = gitlab_net.check_access('git-receive-pack', nil, project, key2, changes, 'ssh')
gitlab_net.check_access('git-receive-pack', nil, project, actor2, changes, 'ssh') access.allowed?.should be_falsey
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed')
end
VCR.use_cassette('ssh-pull-project-denied') do
expect do
gitlab_net.check_access('git-receive-pack', nil, project, actor2, changes, 'ssh')
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed')
end end
end end
it 'should deny push access for host' do it 'should deny push access for host' do
VCR.use_cassette('ssh-push-project-denied-old') do VCR.use_cassette("ssh-push-project-denied") do
expect do access = gitlab_net.check_access('git-upload-pack', nil, project, key2, changes, 'ssh')
gitlab_net.check_access('git-upload-pack', nil, project, actor2, changes, 'ssh') access.allowed?.should be_falsey
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed')
end
VCR.use_cassette('ssh-push-project-denied') do
expect do
gitlab_net.check_access('git-upload-pack', nil, project, actor2, changes, 'ssh')
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed')
end end
end end
it 'should deny push access for host (with user)' do it 'should deny push access for host (with user)' do
VCR.use_cassette('ssh-push-project-denied-with-user-old') do VCR.use_cassette("ssh-push-project-denied-with-user") do
expect do access = gitlab_net.check_access('git-upload-pack', nil, project, 'user-2', changes, 'ssh')
gitlab_net.check_access('git-upload-pack', nil, project, actor2, changes, 'ssh') access.allowed?.should be_falsey
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed')
end
VCR.use_cassette('ssh-push-project-denied-with-user') do
expect do
gitlab_net.check_access('git-upload-pack', nil, project, actor2, changes, 'ssh')
end.to raise_error(AccessDeniedError, 'Git access over SSH is not allowed')
end end
end end
end end
it "raises an exception if the connection fails" do it "raises an exception if the connection fails" do
allow_any_instance_of(Net::HTTP).to receive(:request).and_raise(StandardError) Net::HTTP.any_instance.stub(:request).and_raise(StandardError)
expect { expect {
gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'ssh') gitlab_net.check_access('git-upload-pack', nil, project, 'user-1', changes, 'ssh')
}.to raise_error(GitlabNet::ApiUnreachableError) }.to raise_error(GitlabNet::ApiUnreachableError)
end end
end end
...@@ -450,8 +377,8 @@ describe GitlabNet, vcr: true do ...@@ -450,8 +377,8 @@ describe GitlabNet, vcr: true do
subject { gitlab_net.send :http_client_for, URI('https://localhost/') } subject { gitlab_net.send :http_client_for, URI('https://localhost/') }
before do before do
allow(gitlab_net).to receive(:cert_store) gitlab_net.stub :cert_store
allow(gitlab_net.send(:config)).to receive(:http_settings).and_return({ 'self_signed_cert' => true }) gitlab_net.send(:config).stub(:http_settings) { {'self_signed_cert' => true} }
end end
its(:verify_mode) { should eq(OpenSSL::SSL::VERIFY_NONE) } its(:verify_mode) { should eq(OpenSSL::SSL::VERIFY_NONE) }
...@@ -471,11 +398,11 @@ describe GitlabNet, vcr: true do ...@@ -471,11 +398,11 @@ describe GitlabNet, vcr: true do
subject { gitlab_net.send :http_request_for, :get, url } subject { gitlab_net.send :http_request_for, :get, url }
before do before do
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('user').and_return(user) gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user }
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('password').and_return(password) gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password }
expect(Net::HTTP::Get).to receive(:new).with('/', {}).and_return(get) Net::HTTP::Get.should_receive(:new).with('/', {}).and_return(get)
expect(get).to receive(:basic_auth).with(user, password).once get.should_receive(:basic_auth).with(user, password).once
expect(get).to receive(:set_form_data).with(hash_including(secret_token: secret)).once get.should_receive(:set_form_data).with(hash_including(secret_token: secret)).once
end end
it { should_not be_nil } it { should_not be_nil }
...@@ -485,11 +412,11 @@ describe GitlabNet, vcr: true do ...@@ -485,11 +412,11 @@ describe GitlabNet, vcr: true do
subject { gitlab_net.send :http_request_for, :get, url, params: params, headers: headers } subject { gitlab_net.send :http_request_for, :get, url, params: params, headers: headers }
before do before do
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('user').and_return(user) gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user }
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('password').and_return(password) gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password }
expect(Net::HTTP::Get).to receive(:new).with('/', headers).and_return(get) Net::HTTP::Get.should_receive(:new).with('/', headers).and_return(get)
expect(get).to receive(:basic_auth).with(user, password).once get.should_receive(:basic_auth).with(user, password).once
expect(get).to receive(:set_form_data).with({ 'key1' => 'value1', secret_token: secret }).once get.should_receive(:set_form_data).with({ 'key1' => 'value1', secret_token: secret }).once
end end
it { should_not be_nil } it { should_not be_nil }
...@@ -499,11 +426,11 @@ describe GitlabNet, vcr: true do ...@@ -499,11 +426,11 @@ describe GitlabNet, vcr: true do
subject { gitlab_net.send :http_request_for, :get, url, headers: headers } subject { gitlab_net.send :http_request_for, :get, url, headers: headers }
before do before do
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('user').and_return(user) gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user }
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('password').and_return(password) gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password }
expect(Net::HTTP::Get).to receive(:new).with('/', headers).and_return(get) Net::HTTP::Get.should_receive(:new).with('/', headers).and_return(get)
expect(get).to receive(:basic_auth).with(user, password).once get.should_receive(:basic_auth).with(user, password).once
expect(get).to receive(:set_form_data).with(hash_including(secret_token: secret)).once get.should_receive(:set_form_data).with(hash_including(secret_token: secret)).once
end end
it { should_not be_nil } it { should_not be_nil }
...@@ -514,12 +441,12 @@ describe GitlabNet, vcr: true do ...@@ -514,12 +441,12 @@ describe GitlabNet, vcr: true do
subject { gitlab_net.send :http_request_for, :get, url, options: options } subject { gitlab_net.send :http_request_for, :get, url, options: options }
before do before do
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('user').and_return(user) gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user }
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('password').and_return(password) gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password }
expect(Net::HTTP::Get).to receive(:new).with('/', {}).and_return(get) Net::HTTP::Get.should_receive(:new).with('/', {}).and_return(get)
expect(get).to receive(:basic_auth).with(user, password).once get.should_receive(:basic_auth).with(user, password).once
expect(get).to receive(:body=).with({ 'key2' => 'value2', secret_token: secret }.to_json).once get.should_receive(:body=).with({ 'key2' => 'value2', secret_token: secret }.to_json).once
expect(get).to_not receive(:set_form_data) get.should_not_receive(:set_form_data)
end end
it { should_not be_nil } it { should_not be_nil }
...@@ -530,7 +457,7 @@ describe GitlabNet, vcr: true do ...@@ -530,7 +457,7 @@ describe GitlabNet, vcr: true do
context 'Unix socket' do context 'Unix socket' do
it 'sets the Host header to "localhost"' do it 'sets the Host header to "localhost"' do
gitlab_net = described_class.new gitlab_net = described_class.new
expect(gitlab_net).to receive(:secret_token).and_return(secret) gitlab_net.should_receive(:secret_token).and_return(secret)
request = gitlab_net.send(:http_request_for, :get, URI('http+unix://%2Ffoo')) request = gitlab_net.send(:http_request_for, :get, URI('http+unix://%2Ffoo'))
...@@ -542,12 +469,12 @@ describe GitlabNet, vcr: true do ...@@ -542,12 +469,12 @@ describe GitlabNet, vcr: true do
describe '#cert_store' do describe '#cert_store' do
let(:store) do let(:store) do
double(OpenSSL::X509::Store).tap do |store| double(OpenSSL::X509::Store).tap do |store|
allow(OpenSSL::X509::Store).to receive(:new).and_return(store) OpenSSL::X509::Store.stub(:new) { store }
end end
end end
before :each do before :each do
expect(store).to receive(:set_default_paths).once store.should_receive(:set_default_paths).once
end end
after do after do
...@@ -555,17 +482,17 @@ describe GitlabNet, vcr: true do ...@@ -555,17 +482,17 @@ describe GitlabNet, vcr: true do
end end
it "calls add_file with http_settings['ca_file']" do it "calls add_file with http_settings['ca_file']" do
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('ca_file').and_return('test_file') gitlab_net.send(:config).http_settings.stub(:[]).with('ca_file') { 'test_file' }
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('ca_path').and_return(nil) gitlab_net.send(:config).http_settings.stub(:[]).with('ca_path') { nil }
expect(store).to receive(:add_file).with('test_file') store.should_receive(:add_file).with('test_file')
expect(store).to_not receive(:add_path) store.should_not_receive(:add_path)
end end
it "calls add_path with http_settings['ca_path']" do it "calls add_path with http_settings['ca_path']" do
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('ca_file').and_return(nil) gitlab_net.send(:config).http_settings.stub(:[]).with('ca_file') { nil }
allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('ca_path').and_return('test_path') gitlab_net.send(:config).http_settings.stub(:[]).with('ca_path') { 'test_path' }
expect(store).to_not receive(:add_file) store.should_not_receive(:add_file)
expect(store).to receive(:add_path).with('test_path') store.should_receive(:add_path).with('test_path')
end end
end end
end end
...@@ -5,13 +5,13 @@ require 'gitlab_post_receive' ...@@ -5,13 +5,13 @@ require 'gitlab_post_receive'
describe GitlabPostReceive do describe GitlabPostReceive do
let(:repository_path) { "/home/git/repositories" } let(:repository_path) { "/home/git/repositories" }
let(:repo_name) { 'dzaporozhets/gitlab-ci' } let(:repo_name) { 'dzaporozhets/gitlab-ci' }
let(:gl_id) { 'key-123' } let(:actor) { 'key-123' }
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" } let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") } let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) } let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
let(:repo_path) { File.join(repository_path, repo_name) + ".git" } let(:repo_path) { File.join(repository_path, repo_name) + ".git" }
let(:gl_repository) { "project-1" } let(:gl_repository) { "project-1" }
let(:gitlab_post_receive) { GitlabPostReceive.new(gl_repository, repo_path, gl_id, wrongly_encoded_changes) } let(:gitlab_post_receive) { GitlabPostReceive.new(gl_repository, repo_path, actor, wrongly_encoded_changes) }
let(:broadcast_message) { "test " * 10 + "message " * 10 } let(:broadcast_message) { "test " * 10 + "message " * 10 }
let(:enqueued_at) { Time.new(2016, 6, 23, 6, 59) } let(:enqueued_at) { Time.new(2016, 6, 23, 6, 59) }
let(:new_merge_request_urls) do let(:new_merge_request_urls) do
...@@ -31,7 +31,7 @@ describe GitlabPostReceive do ...@@ -31,7 +31,7 @@ describe GitlabPostReceive do
before do before do
$logger = double('logger').as_null_object # Global vars are bad $logger = double('logger').as_null_object # Global vars are bad
allow_any_instance_of(GitlabConfig).to receive(:repos_path).and_return(repository_path) GitlabConfig.any_instance.stub(repos_path: repository_path)
end end
describe "#exec" do describe "#exec" do
...@@ -63,7 +63,7 @@ describe GitlabPostReceive do ...@@ -63,7 +63,7 @@ describe GitlabPostReceive do
context 'when contains long url string at end' do context 'when contains long url string at end' do
let(:broadcast_message) { "test " * 10 + "message " * 10 + "https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url" } let(:broadcast_message) { "test " * 10 + "message " * 10 + "https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url" }
it 'doesnt truncate url' do it 'doesnt truncate url' do
expect_any_instance_of(GitlabNet).to receive(:post_receive).and_return(response) expect_any_instance_of(GitlabNet).to receive(:post_receive).and_return(response)
assert_broadcast_message_printed_keep_long_url_end(gitlab_post_receive) assert_broadcast_message_printed_keep_long_url_end(gitlab_post_receive)
assert_new_mr_printed(gitlab_post_receive) assert_new_mr_printed(gitlab_post_receive)
...@@ -75,7 +75,7 @@ describe GitlabPostReceive do ...@@ -75,7 +75,7 @@ describe GitlabPostReceive do
context 'when contains long url string at start' do context 'when contains long url string at start' do
let(:broadcast_message) { "https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url " + "test " * 10 + "message " * 11} let(:broadcast_message) { "https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url " + "test " * 10 + "message " * 11}
it 'doesnt truncate url' do it 'doesnt truncate url' do
expect_any_instance_of(GitlabNet).to receive(:post_receive).and_return(response) expect_any_instance_of(GitlabNet).to receive(:post_receive).and_return(response)
assert_broadcast_message_printed_keep_long_url_start(gitlab_post_receive) assert_broadcast_message_printed_keep_long_url_start(gitlab_post_receive)
assert_new_mr_printed(gitlab_post_receive) assert_new_mr_printed(gitlab_post_receive)
...@@ -87,7 +87,7 @@ describe GitlabPostReceive do ...@@ -87,7 +87,7 @@ describe GitlabPostReceive do
context 'when contains long url string in middle' do context 'when contains long url string in middle' do
let(:broadcast_message) { "test " * 11 + "https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url " + "message " * 11} let(:broadcast_message) { "test " * 11 + "https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url " + "message " * 11}
it 'doesnt truncate url' do it 'doesnt truncate url' do
expect_any_instance_of(GitlabNet).to receive(:post_receive).and_return(response) expect_any_instance_of(GitlabNet).to receive(:post_receive).and_return(response)
assert_broadcast_message_printed_keep_long_url_middle(gitlab_post_receive) assert_broadcast_message_printed_keep_long_url_middle(gitlab_post_receive)
assert_new_mr_printed(gitlab_post_receive) assert_new_mr_printed(gitlab_post_receive)
...@@ -198,7 +198,7 @@ describe GitlabPostReceive do ...@@ -198,7 +198,7 @@ describe GitlabPostReceive do
expect(gitlab_post_receive).to receive(:puts).with( expect(gitlab_post_receive).to receive(:puts).with(
" message message message message message message message message" " message message message message message message message message"
).ordered ).ordered
expect(gitlab_post_receive).to receive(:puts).with( expect(gitlab_post_receive).to receive(:puts).with(
"https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url" "https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url"
).ordered ).ordered
...@@ -215,7 +215,7 @@ describe GitlabPostReceive do ...@@ -215,7 +215,7 @@ describe GitlabPostReceive do
"========================================================================" "========================================================================"
).ordered ).ordered
expect(gitlab_post_receive).to receive(:puts).ordered expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with( expect(gitlab_post_receive).to receive(:puts).with(
"https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url" "https://localhost:5000/test/a/really/long/url/that/is/in/the/broadcast/message/do-not-truncate-when-url"
).ordered ).ordered
...@@ -244,7 +244,7 @@ describe GitlabPostReceive do ...@@ -244,7 +244,7 @@ describe GitlabPostReceive do
"========================================================================" "========================================================================"
).ordered ).ordered
expect(gitlab_post_receive).to receive(:puts).ordered expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with( expect(gitlab_post_receive).to receive(:puts).with(
" test test test test test test test test test test test" " test test test test test test test test test test test"
).ordered ).ordered
......
require_relative 'spec_helper' require_relative 'spec_helper'
require_relative '../lib/gitlab_shell' require_relative '../lib/gitlab_shell'
require_relative '../lib/action' require_relative '../lib/gitlab_access_status'
describe GitlabShell do describe GitlabShell do
before do before do
...@@ -8,208 +8,565 @@ describe GitlabShell do ...@@ -8,208 +8,565 @@ describe GitlabShell do
FileUtils.mkdir_p(tmp_repos_path) FileUtils.mkdir_p(tmp_repos_path)
end end
after { FileUtils.rm_rf(tmp_repos_path) } after do
FileUtils.rm_rf(tmp_repos_path)
end
subject do
ARGV[0] = gl_id
GitlabShell.new(gl_id).tap do |shell|
shell.stub(exec_cmd: :exec_called)
shell.stub(api: api)
end
end
subject { described_class.new(who) } let(:gitaly_check_access) { GitAccessStatus.new(
true,
'ok',
gl_repository: gl_repository,
gl_id: gl_id,
gl_username: gl_username,
repository_path: repo_path,
gitaly: { 'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default'} , 'address' => 'unix:gitaly.socket' },
git_protocol: git_protocol
)
}
let(:api) do
double(GitlabNet).tap do |api|
api.stub(discover: { 'name' => 'John Doe', 'username' => 'testuser' })
api.stub(check_access: GitAccessStatus.new(
true,
'ok',
gl_repository: gl_repository,
gl_id: gl_id,
gl_username: gl_username,
repository_path: repo_path,
gitaly: nil,
git_protocol: git_protocol))
api.stub(two_factor_recovery_codes: {
'success' => true,
'recovery_codes' => %w[f67c514de60c4953 41278385fc00c1e0]
})
end
end
let(:who) { 'key-1' } let(:gl_id) { "key-#{rand(100) + 100}" }
let(:audit_usernames) { true } let(:ssh_cmd) { nil }
let(:actor) { Actor.new_from(who, audit_usernames: audit_usernames) }
let(:tmp_repos_path) { File.join(ROOT_PATH, 'tmp', 'repositories') } let(:tmp_repos_path) { File.join(ROOT_PATH, 'tmp', 'repositories') }
let(:repo_name) { 'gitlab-ci.git' } let(:repo_name) { 'gitlab-ci.git' }
let(:repo_path) { File.join(tmp_repos_path, repo_name) } let(:repo_path) { File.join(tmp_repos_path, repo_name) }
let(:gl_repository) { 'project-1' } let(:gl_repository) { 'project-1' }
let(:gl_id) { 'user-1' }
let(:gl_username) { 'testuser' } let(:gl_username) { 'testuser' }
let(:git_protocol) { 'version=2' } let(:git_protocol) { 'version=2' }
let(:api) { double(GitlabNet) }
let(:config) { double(GitlabConfig) }
let(:gitaly_action) { Action::Gitaly.new(
actor,
gl_repository,
gl_username,
git_protocol,
repo_path,
{ 'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default' } , 'address' => 'unix:gitaly.socket' })
}
let(:api_2fa_recovery_action) { Action::API2FARecovery.new(actor) }
let(:git_lfs_authenticate_action) { Action::GitLFSAuthenticate.new(actor, repo_name) }
before do before do
allow(GitlabConfig).to receive(:new).and_return(config) GitlabConfig.any_instance.stub(audit_usernames: false)
allow(config).to receive(:audit_usernames).and_return(audit_usernames) end
allow(Actor).to receive(:new_from).with(who, audit_usernames: audit_usernames).and_return(actor) describe :initialize do
let(:ssh_cmd) { 'git-receive-pack' }
allow(GitlabNet).to receive(:new).and_return(api) its(:gl_id) { should == gl_id }
allow(api).to receive(:discover).with(actor).and_return('username' => gl_username)
end end
describe '#exec' do describe :parse_cmd do
context "when we don't have a valid user" do describe 'git' do
before do context 'w/o namespace' do
allow(api).to receive(:discover).with(actor).and_return(nil) let(:ssh_args) { %w(git-upload-pack gitlab-ci.git) }
before do
subject.send :parse_cmd, ssh_args
end
its(:repo_name) { should == 'gitlab-ci.git' }
its(:command) { should == 'git-upload-pack' }
end end
it 'prints Welcome.. and returns true' do context 'namespace' do
expect { let(:repo_name) { 'dmitriy.zaporozhets/gitlab-ci.git' }
expect(subject.exec(nil)).to be_truthy let(:ssh_args) { %w(git-upload-pack dmitriy.zaporozhets/gitlab-ci.git) }
}.to output("Welcome to GitLab, Anonymous!\n").to_stdout
before do
subject.send :parse_cmd, ssh_args
end
its(:repo_name) { should == 'dmitriy.zaporozhets/gitlab-ci.git' }
its(:command) { should == 'git-upload-pack' }
end end
end
context 'when we have a valid user' do context 'with an invalid number of arguments' do
context 'when origin_cmd is nil' do let(:ssh_args) { %w(foobar) }
it 'prints Welcome.. and returns true' do
expect { it "should raise an DisallowedCommandError" do
expect(subject.exec(nil)).to be_truthy expect { subject.send :parse_cmd, ssh_args }.to raise_error(GitlabShell::DisallowedCommandError)
}.to output("Welcome to GitLab, @testuser!\n").to_stdout
end end
end end
context 'when origin_cmd is empty' do context 'with an API command' do
it 'prints Welcome.. and returns true' do before do
expect { subject.send :parse_cmd, ssh_args
expect(subject.exec('')).to be_truthy end
}.to output("Welcome to GitLab, @testuser!\n").to_stdout
context 'when generating recovery codes' do
let(:ssh_args) { %w(2fa_recovery_codes) }
it 'sets the correct command' do
expect(subject.command).to eq('2fa_recovery_codes')
end
it 'does not set repo name' do
expect(subject.repo_name).to be_nil
end
end end
end end
end end
context 'when origin_cmd is invalid' do describe 'git-lfs' do
it 'prints a message to stderr and returns false' do let(:repo_name) { 'dzaporozhets/gitlab.git' }
expect { let(:ssh_args) { %w(git-lfs-authenticate dzaporozhets/gitlab.git download) }
expect(subject.exec("git-invalid-command #{repo_name}")).to be_falsey
}.to output("GitLab: Disallowed command\n").to_stderr before do
subject.send :parse_cmd, ssh_args
end end
its(:repo_name) { should == 'dzaporozhets/gitlab.git' }
its(:command) { should == 'git-lfs-authenticate' }
its(:git_access) { should == 'git-upload-pack' }
end end
context 'when origin_cmd is valid, but incomplete' do describe 'git-lfs old clients' do
it 'prints a message to stderr and returns false' do let(:repo_name) { 'dzaporozhets/gitlab.git' }
expect { let(:ssh_args) { %w(git-lfs-authenticate dzaporozhets/gitlab.git download long_oid) }
expect(subject.exec('git-upload-pack')).to be_falsey
}.to output("GitLab: Disallowed command\n").to_stderr before do
subject.send :parse_cmd, ssh_args
end end
its(:repo_name) { should == 'dzaporozhets/gitlab.git' }
its(:command) { should == 'git-lfs-authenticate' }
its(:git_access) { should == 'git-upload-pack' }
end end
end
context 'when origin_cmd is git-lfs-authenticate' do describe :exec do
context 'but incomplete' do let(:gitaly_message) do
it 'prints a message to stderr and returns false' do JSON.dump(
expect { 'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default' },
expect(subject.exec('git-lfs-authenticate')).to be_falsey 'gl_repository' => gl_repository,
}.to output("GitLab: Disallowed command\n").to_stderr 'gl_id' => gl_id,
end 'gl_username' => gl_username,
'git_protocol' => git_protocol
)
end
before do
allow(ENV).to receive(:[]).with('GIT_PROTOCOL').and_return(git_protocol)
end
shared_examples_for 'upload-pack' do |command|
let(:ssh_cmd) { "#{command} gitlab-ci.git" }
after { subject.exec(ssh_cmd) }
it "should process the command" do
subject.should_receive(:process_cmd).with(%w(git-upload-pack gitlab-ci.git))
end end
context 'but invalid' do it "should execute the command" do
it 'prints a message to stderr and returns false' do subject.should_receive(:exec_cmd).with('git-upload-pack', repo_path)
expect { end
expect(subject.exec("git-lfs-authenticate #{repo_name} invalid")).to be_falsey
}.to output("GitLab: Disallowed command\n").to_stderr it "should log the command execution" do
end message = "executing git command"
user_string = "user with id #{gl_id}"
$logger.should_receive(:info).with(message, command: "git-upload-pack #{repo_path}", user: user_string)
end
it "should use usernames if configured to do so" do
GitlabConfig.any_instance.stub(audit_usernames: true)
$logger.should_receive(:info).with("executing git command", hash_including(user: 'testuser'))
end
end
context 'git-upload-pack' do
it_behaves_like 'upload-pack', 'git-upload-pack'
end
context 'git upload-pack' do
it_behaves_like 'upload-pack', 'git upload-pack'
end
context 'gitaly-upload-pack' do
let(:ssh_cmd) { "git-upload-pack gitlab-ci.git" }
before do
api.stub(check_access: gitaly_check_access)
end
after { subject.exec(ssh_cmd) }
it "should process the command" do
subject.should_receive(:process_cmd).with(%w(git-upload-pack gitlab-ci.git))
end
it "should execute the command" do
subject.should_receive(:exec_cmd).with(File.join(ROOT_PATH, "bin/gitaly-upload-pack"), 'unix:gitaly.socket', gitaly_message)
end
it "should log the command execution" do
message = "executing git command"
user_string = "user with id #{gl_id}"
$logger.should_receive(:info).with(message, command: "gitaly-upload-pack unix:gitaly.socket #{gitaly_message}", user: user_string)
end
it "should use usernames if configured to do so" do
GitlabConfig.any_instance.stub(audit_usernames: true)
$logger.should_receive(:info).with("executing git command", hash_including(user: 'testuser'))
end end
end end
context 'when origin_cmd is 2fa_recovery_codes' do context 'git-receive-pack' do
let(:origin_cmd) { '2fa_recovery_codes' } let(:ssh_cmd) { "git-receive-pack gitlab-ci.git" }
let(:git_access) { '2fa_recovery_codes' } after { subject.exec(ssh_cmd) }
it "should process the command" do
subject.should_receive(:process_cmd).with(%w(git-receive-pack gitlab-ci.git))
end
it "should execute the command" do
subject.should_receive(:exec_cmd).with('git-receive-pack', repo_path)
end
it "should log the command execution" do
message = "executing git command"
user_string = "user with id #{gl_id}"
$logger.should_receive(:info).with(message, command: "git-receive-pack #{repo_path}", user: user_string)
end
end
context 'gitaly-receive-pack' do
let(:ssh_cmd) { "git-receive-pack gitlab-ci.git" }
before do before do
expect(Action::API2FARecovery).to receive(:new).with(actor).and_return(api_2fa_recovery_action) api.stub(check_access: gitaly_check_access)
end
after { subject.exec(ssh_cmd) }
it "should process the command" do
subject.should_receive(:process_cmd).with(%w(git-receive-pack gitlab-ci.git))
end
it "should execute the command" do
subject.should_receive(:exec_cmd).with(File.join(ROOT_PATH, "bin/gitaly-receive-pack"), 'unix:gitaly.socket', gitaly_message)
end
it "should log the command execution" do
message = "executing git command"
user_string = "user with id #{gl_id}"
$logger.should_receive(:info).with(message, command: "gitaly-receive-pack unix:gitaly.socket #{gitaly_message}", user: user_string)
end
it "should use usernames if configured to do so" do
GitlabConfig.any_instance.stub(audit_usernames: true)
$logger.should_receive(:info).with("executing git command", hash_including(user: 'testuser'))
end
end
shared_examples_for 'upload-archive' do |command|
let(:ssh_cmd) { "#{command} gitlab-ci.git" }
let(:exec_cmd_params) { ['git-upload-archive', repo_path] }
let(:exec_cmd_log_params) { exec_cmd_params }
after { subject.exec(ssh_cmd) }
it "should process the command" do
subject.should_receive(:process_cmd).with(%w(git-upload-archive gitlab-ci.git))
end
it "should execute the command" do
subject.should_receive(:exec_cmd).with(*exec_cmd_params)
end end
it 'returns true' do it "should log the command execution" do
expect(api_2fa_recovery_action).to receive(:execute).with('2fa_recovery_codes', %w{ 2fa_recovery_codes }).and_return(true) message = "executing git command"
expect(subject.exec(origin_cmd)).to be_truthy user_string = "user with id #{gl_id}"
$logger.should_receive(:info).with(message, command: exec_cmd_log_params.join(' '), user: user_string)
end
it "should use usernames if configured to do so" do
GitlabConfig.any_instance.stub(audit_usernames: true)
$logger.should_receive(:info).with("executing git command", hash_including(user: 'testuser'))
end end
end end
context 'when access to the repo is denied' do context 'git-upload-archive' do
it_behaves_like 'upload-archive', 'git-upload-archive'
end
context 'git upload-archive' do
it_behaves_like 'upload-archive', 'git upload-archive'
end
context 'gitaly-upload-archive' do
before do before do
expect(api).to receive(:check_access).with('git-upload-pack', nil, repo_name, actor, '_any').and_raise(AccessDeniedError, 'Sorry, access denied') api.stub(check_access: gitaly_check_access)
end end
it 'prints a message to stderr and returns false' do it_behaves_like 'upload-archive', 'git-upload-archive' do
expect($stderr).to receive(:puts).with('GitLab: Sorry, access denied') let(:gitaly_executable) { "gitaly-upload-archive" }
expect(subject.exec("git-upload-pack #{repo_name}")).to be_falsey let(:exec_cmd_params) do
[
File.join(ROOT_PATH, "bin", gitaly_executable),
'unix:gitaly.socket',
gitaly_message
]
end
let(:exec_cmd_log_params) do
[gitaly_executable, 'unix:gitaly.socket', gitaly_message]
end
end end
end end
context 'when the API is unavailable' do context 'arbitrary command' do
let(:ssh_cmd) { 'arbitrary command' }
after { subject.exec(ssh_cmd) }
it "should not process the command" do
subject.should_not_receive(:process_cmd)
end
it "should not execute the command" do
subject.should_not_receive(:exec_cmd)
end
it "should log the attempt" do
message = 'Denied disallowed command'
user_string = "user with id #{gl_id}"
$logger.should_receive(:warn).with(message, command: 'arbitrary command', user: user_string)
end
end
context 'no command' do
after { subject.exec(nil) }
it "should call api.discover" do
api.should_receive(:discover).with(gl_id)
end
end
context "failed connection" do
let(:ssh_cmd) { 'git-upload-pack gitlab-ci.git' }
before do before do
expect(api).to receive(:check_access).with('git-upload-pack', nil, repo_name, actor, '_any').and_raise(GitlabNet::ApiUnreachableError) api.stub(:check_access).and_raise(GitlabNet::ApiUnreachableError)
end
after { subject.exec(ssh_cmd) }
it "should not process the command" do
subject.should_not_receive(:process_cmd)
end end
it 'prints a message to stderr and returns false' do it "should not execute the command" do
expect($stderr).to receive(:puts).with('GitLab: Failed to authorize your Git request: internal API unreachable') subject.should_not_receive(:exec_cmd)
expect(subject.exec("git-upload-pack #{repo_name}")).to be_falsey
end end
end end
context 'when access has been verified OK' do context 'with an API command' do
before do before do
expect(api).to receive(:check_access).with(git_access, nil, repo_name, actor, '_any').and_return(gitaly_action) allow(subject).to receive(:continue?).and_return(true)
end end
context 'when origin_cmd is git-upload-pack' do context 'when generating recovery codes' do
let(:origin_cmd) { 'git-upload-pack' } let(:ssh_cmd) { '2fa_recovery_codes' }
let(:git_access) { 'git-upload-pack' } after do
subject.exec(ssh_cmd)
end
it 'returns true' do it 'does not call verify_access' do
expect(gitaly_action).to receive(:execute).with('git-upload-pack', %W{git-upload-pack #{repo_name}}).and_return(true) expect(subject).not_to receive(:verify_access)
expect(subject.exec("#{origin_cmd} #{repo_name}")).to be_truthy
end end
context 'but repo path is invalid' do it 'calls the corresponding method' do
it 'prints a message to stderr and returns false' do expect(subject).to receive(:api_2fa_recovery_codes)
expect(gitaly_action).to receive(:execute).with('git-upload-pack', %W{git-upload-pack #{repo_name}}).and_raise(InvalidRepositoryPathError)
expect($stderr).to receive(:puts).with('GitLab: Invalid repository path')
expect(subject.exec("#{origin_cmd} #{repo_name}")).to be_falsey
end
end end
context "but we're using an old git version for Windows 2.14" do it 'outputs recovery codes' do
it 'returns true' do expect($stdout).to receive(:puts)
expect(gitaly_action).to receive(:execute).with('git-upload-pack', %W{git-upload-pack #{repo_name}}).and_return(true) .with(/f67c514de60c4953\n41278385fc00c1e0/)
expect(subject.exec("git upload-pack #{repo_name}")).to be_truthy #NOTE: 'git upload-pack' vs. 'git-upload-pack' end
context 'when the process is unsuccessful' do
it 'displays the error to the user' do
api.stub(two_factor_recovery_codes: {
'success' => false,
'message' => 'Could not find the given key'
})
expect($stdout).to receive(:puts)
.with(/Could not find the given key/)
end end
end end
end end
end
end
describe :validate_access do
let(:ssh_cmd) { "git-upload-pack gitlab-ci.git" }
describe 'check access with api' do
after { subject.exec(ssh_cmd) }
it "should call api.check_access" do
api.should_receive(:check_access).with('git-upload-pack', nil, 'gitlab-ci.git', gl_id, '_any', 'ssh')
end
context 'when origin_cmd is git-lfs-authenticate' do it "should disallow access and log the attempt if check_access returns false status" do
let(:origin_cmd) { 'git-lfs-authenticate' } api.stub(check_access: GitAccessStatus.new(
let(:lfs_access) { double(GitlabLfsAuthentication, authentication_payload: fake_payload)} false,
'denied',
gl_repository: nil,
gl_id: nil,
gl_username: nil,
repository_path: nil,
gitaly: nil,
git_protocol: nil))
message = 'Access denied'
user_string = "user with id #{gl_id}"
$logger.should_receive(:warn).with(message, command: 'git-upload-pack gitlab-ci.git', user: user_string)
end
end
describe 'set the repository path' do
context 'with a correct path' do
before { subject.exec(ssh_cmd) }
its(:repo_path) { should == repo_path }
end
context "with a path that doesn't match an absolute path" do
before do before do
expect(Action::GitLFSAuthenticate).to receive(:new).with(actor, repo_name).and_return(git_lfs_authenticate_action) File.stub(:absolute_path) { 'y/gitlab-ci.git' }
end
it "refuses to assign the path" do
$stderr.should_receive(:puts).with("GitLab: Invalid repository path")
expect(subject.exec(ssh_cmd)).to be_falsey
end end
end
end
end
context 'upload' do describe :exec_cmd do
let(:git_access) { 'git-receive-pack' } let(:shell) { GitlabShell.new(gl_id) }
let(:env) do
{
'HOME' => ENV['HOME'],
'PATH' => ENV['PATH'],
'LD_LIBRARY_PATH' => ENV['LD_LIBRARY_PATH'],
'LANG' => ENV['LANG'],
'GL_ID' => gl_id,
'GL_PROTOCOL' => 'ssh',
'GL_REPOSITORY' => gl_repository,
'GL_USERNAME' => 'testuser'
}
end
let(:exec_options) { { unsetenv_others: true, chdir: ROOT_PATH } }
before do
Kernel.stub(:exec)
shell.gl_repository = gl_repository
shell.git_protocol = git_protocol
shell.instance_variable_set(:@username, gl_username)
end
it 'returns true' do it "uses Kernel::exec method" do
expect(git_lfs_authenticate_action).to receive(:execute).with('git-lfs-authenticate', %w{ git-lfs-authenticate gitlab-ci.git upload }).and_return(true) Kernel.should_receive(:exec).with(env, 1, 2, exec_options).once
expect(subject.exec("#{origin_cmd} #{repo_name} upload")).to be_truthy shell.send :exec_cmd, 1, 2
end end
it "refuses to execute a lone non-array argument" do
expect { shell.send :exec_cmd, 1 }.to raise_error(GitlabShell::DisallowedCommandError)
end
it "allows one argument if it is an array" do
Kernel.should_receive(:exec).with(env, [1, 2], exec_options).once
shell.send :exec_cmd, [1, 2]
end
context "when specifying a git_tracing log file" do
let(:git_trace_log_file) { '/tmp/git_trace_performance.log' }
before do
GitlabConfig.any_instance.stub(git_trace_log_file: git_trace_log_file)
shell
end
it "uses GIT_TRACE_PERFORMANCE" do
expected_hash = hash_including(
'GIT_TRACE' => git_trace_log_file,
'GIT_TRACE_PACKET' => git_trace_log_file,
'GIT_TRACE_PERFORMANCE' => git_trace_log_file
)
Kernel.should_receive(:exec).with(expected_hash, [1, 2], exec_options).once
shell.send :exec_cmd, [1, 2]
end
context "when provides a relative path" do
let(:git_trace_log_file) { 'git_trace_performance.log' }
it "does not uses GIT_TRACE*" do
# If we try to use it we'll show a warning to the users
expected_hash = hash_excluding(
'GIT_TRACE', 'GIT_TRACE_PACKET', 'GIT_TRACE_PERFORMANCE'
)
Kernel.should_receive(:exec).with(expected_hash, [1, 2], exec_options).once
shell.send :exec_cmd, [1, 2]
end end
context 'download' do it "writes an entry on the log" do
let(:git_access) { 'git-upload-pack' } message = 'git trace log path must be absolute, ignoring'
it 'returns true' do expect($logger).to receive(:warn).
expect(git_lfs_authenticate_action).to receive(:execute).with('git-lfs-authenticate', %w{ git-lfs-authenticate gitlab-ci.git download }).and_return(true) with(message, git_trace_log_file: git_trace_log_file)
expect(subject.exec("#{origin_cmd} #{repo_name} download")).to be_truthy
end
context 'for old git-lfs clients' do Kernel.should_receive(:exec).with(env, [1, 2], exec_options).once
it 'returns true' do shell.send :exec_cmd, [1, 2]
expect(git_lfs_authenticate_action).to receive(:execute).with('git-lfs-authenticate', %w{ git-lfs-authenticate gitlab-ci.git download long_oid }).and_return(true) end
expect(subject.exec("#{origin_cmd} #{repo_name} download long_oid")).to be_truthy end
end
end context "when provides a file not writable" do
before do
expect(File).to receive(:open).with(git_trace_log_file, 'a').and_raise(Errno::EACCES)
end
it "does not uses GIT_TRACE*" do
# If we try to use it we'll show a warning to the users
expected_hash = hash_excluding(
'GIT_TRACE', 'GIT_TRACE_PACKET', 'GIT_TRACE_PERFORMANCE'
)
Kernel.should_receive(:exec).with(expected_hash, [1, 2], exec_options).once
shell.send :exec_cmd, [1, 2]
end
it "writes an entry on the log" do
message = 'Failed to open git trace log file'
error = 'Permission denied'
expect($logger).to receive(:warn).
with(message, git_trace_log_file: git_trace_log_file, error: error)
Kernel.should_receive(:exec).with(env, [1, 2], exec_options).once
shell.send :exec_cmd, [1, 2]
end end
end end
end end
end end
describe :api do
let(:shell) { GitlabShell.new(gl_id) }
subject { shell.send :api }
it { should be_a(GitlabNet) }
end
end end
...@@ -5,8 +5,8 @@ describe NamesHelper do ...@@ -5,8 +5,8 @@ describe NamesHelper do
include NamesHelper include NamesHelper
describe :extract_ref_name do describe :extract_ref_name do
it { expect(extract_ref_name('refs/heads/awesome-feature')).to eql 'awesome-feature' } it { extract_ref_name('refs/heads/awesome-feature').should == 'awesome-feature' }
it { expect(extract_ref_name('refs/tags/v2.2.1')).to eql 'v2.2.1' } it { extract_ref_name('refs/tags/v2.2.1').should == 'v2.2.1' }
it { expect(extract_ref_name('refs/tags/releases/v2.2.1')).to eql 'releases/v2.2.1' } it { extract_ref_name('refs/tags/releases/v2.2.1').should == 'releases/v2.2.1' }
end end
end end
...@@ -5,7 +5,7 @@ http_interactions: ...@@ -5,7 +5,7 @@ http_interactions:
uri: http://localhost:3000/api/v4/internal/allowed uri: http://localhost:3000/api/v4/internal/allowed
body: body:
encoding: US-ASCII encoding: US-ASCII
string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers: headers:
Accept-Encoding: Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
......
...@@ -5,7 +5,7 @@ http_interactions: ...@@ -5,7 +5,7 @@ http_interactions:
uri: http://localhost:3000/api/v4/internal/allowed uri: http://localhost:3000/api/v4/internal/allowed
body: body:
encoding: US-ASCII encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers: headers:
Accept-Encoding: Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
......
---
http_interactions:
- request:
method: get
uri: http://localhost:3000/api/v4/internal/discover?key_id=1
body:
encoding: US-ASCII
string: secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 404
message: Not Found
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '42'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:49 GMT
Etag:
- W/"63b4ab301951bea83c4fc398eba8e307"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- dc11b8d4-1972-417b-8305-2c35c849405c
X-Runtime:
- '0.230170'
body:
encoding: UTF-8
string: '{"message":"404 Not found"}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:49 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 500
message: Internal Server Error
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 67ab4954-19e6-42ce-aae6-55c8ae5a365e
X-Runtime:
- '0.230871'
body:
encoding: UTF-8
string: '""'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 500
message: Internal Server Error
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 67ab4954-19e6-42ce-aae6-55c8ae5a365e
X-Runtime:
- '0.230871'
body:
encoding: UTF-8
string: '{"status":false,"message":"An internal server error occurred"}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=http&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 200
message: OK
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '62'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:32:01 GMT
Etag:
- W/"71e09fcf8a60a03cd1acc22806386ead"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 70bdecc9-0078-4a4b-aa6b-cac1b2578886
X-Runtime:
- '0.324202'
body:
encoding: UTF-8
string: '{"status":false,"message":"Pulling over HTTP is not allowed."}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:32:01 GMT
recorded_with: VCR 2.4.0
...@@ -17,8 +17,8 @@ http_interactions: ...@@ -17,8 +17,8 @@ http_interactions:
- application/x-www-form-urlencoded - application/x-www-form-urlencoded
response: response:
status: status:
code: 401 code: 200
message: Unauthorized message: OK
headers: headers:
Cache-Control: Cache-Control:
- max-age=0, private, must-revalidate - max-age=0, private, must-revalidate
......
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=http&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 200
message: OK
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '62'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:32:01 GMT
Etag:
- W/"7f14e23ac07cc8b0a53c567fcf9432fd"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 573f3584-87c6-41cb-a5bf-5e7ee76d4250
X-Runtime:
- '0.266135'
body:
encoding: UTF-8
string: '{"status":false,"message":"Pushing over HTTP is not allowed."}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:32:01 GMT
recorded_with: VCR 2.4.0
...@@ -17,8 +17,8 @@ http_interactions: ...@@ -17,8 +17,8 @@ http_interactions:
- application/x-www-form-urlencoded - application/x-www-form-urlencoded
response: response:
status: status:
code: 401 code: 200
message: Unauthorized message: OK
headers: headers:
Cache-Control: Cache-Control:
- max-age=0, private, must-revalidate - max-age=0, private, must-revalidate
......
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 200
message: OK
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '63'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 12:23:57 GMT
Etag:
- W/"76a32010244f80700d5e1ba8a55d094c"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 096ae253-c6fe-4360-b4d4-48f4b5435ca6
X-Runtime:
- '6.377187'
body:
encoding: UTF-8
string: '{"status":false,"message":"Git access over SSH is not allowed"}'
http_version:
recorded_at: Wed, 21 Jun 2017 12:23:57 GMT
recorded_with: VCR 2.4.0
...@@ -17,8 +17,8 @@ http_interactions: ...@@ -17,8 +17,8 @@ http_interactions:
- application/x-www-form-urlencoded - application/x-www-form-urlencoded
response: response:
status: status:
code: 401 code: 200
message: Unauthorized message: OK
headers: headers:
Cache-Control: Cache-Control:
- max-age=0, private, must-revalidate - max-age=0, private, must-revalidate
......
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=2&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 200
message: OK
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '63'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 12:24:04 GMT
Etag:
- W/"76a32010244f80700d5e1ba8a55d094c"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- c843a5a3-fc08-46eb-aa45-caceae515638
X-Runtime:
- '7.359835'
body:
encoding: UTF-8
string: '{"status":false,"message":"Git access over SSH is not allowed"}'
http_version:
recorded_at: Wed, 21 Jun 2017 12:24:04 GMT
recorded_with: VCR 2.4.0
...@@ -17,8 +17,8 @@ http_interactions: ...@@ -17,8 +17,8 @@ http_interactions:
- application/x-www-form-urlencoded - application/x-www-form-urlencoded
response: response:
status: status:
code: 401 code: 200
message: Unauthorized message: OK
headers: headers:
Cache-Control: Cache-Control:
- max-age=0, private, must-revalidate - max-age=0, private, must-revalidate
......
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 200
message: OK
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '63'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 12:23:57 GMT
Etag:
- W/"76a32010244f80700d5e1ba8a55d094c"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 93620e06-fda9-4be5-855e-300f5d62fa3c
X-Runtime:
- '0.207159'
body:
encoding: UTF-8
string: '{"status":false,"message":"Git access over SSH is not allowed"}'
http_version:
recorded_at: Wed, 21 Jun 2017 12:23:57 GMT
recorded_with: VCR 2.4.0
...@@ -17,8 +17,8 @@ http_interactions: ...@@ -17,8 +17,8 @@ http_interactions:
- application/x-www-form-urlencoded - application/x-www-form-urlencoded
response: response:
status: status:
code: 401 code: 200
message: Unauthorized message: OK
headers: headers:
Cache-Control: Cache-Control:
- max-age=0, private, must-revalidate - max-age=0, private, must-revalidate
......
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=2&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 200
message: OK
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '63'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 12:24:04 GMT
Etag:
- W/"76a32010244f80700d5e1ba8a55d094c"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 8ce54f29-9ed0-46e5-aedb-37edaa3d52da
X-Runtime:
- '0.228256'
body:
encoding: UTF-8
string: '{"status":false,"message":"Git access over SSH is not allowed"}'
http_version:
recorded_at: Wed, 21 Jun 2017 12:24:04 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&user_id=2&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 200
message: OK
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '63'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 12:24:05 GMT
Etag:
- W/"76a32010244f80700d5e1ba8a55d094c"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 3b242d73-d860-48ac-8fef-80e2d0d3daca
X-Runtime:
- '0.342469'
body:
encoding: UTF-8
string: '{"status":false,"message":"Git access over SSH is not allowed"}'
http_version:
recorded_at: Wed, 21 Jun 2017 12:24:05 GMT
recorded_with: VCR 2.4.0
...@@ -17,8 +17,8 @@ http_interactions: ...@@ -17,8 +17,8 @@ http_interactions:
- application/x-www-form-urlencoded - application/x-www-form-urlencoded
response: response:
status: status:
code: 401 code: 200
message: Unauthorized message: OK
headers: headers:
Cache-Control: Cache-Control:
- max-age=0, private, must-revalidate - max-age=0, private, must-revalidate
......
...@@ -17,8 +17,8 @@ http_interactions: ...@@ -17,8 +17,8 @@ http_interactions:
- application/x-www-form-urlencoded - application/x-www-form-urlencoded
response: response:
status: status:
code: 401 code: 200
message: Unauthorized message: OK
headers: headers:
Cache-Control: Cache-Control:
- max-age=0, private, must-revalidate - max-age=0, private, must-revalidate
......
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