Commit 3aaf4751 authored by Robert Speicher's avatar Robert Speicher

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

Refactor that focuses on SRP improvements

See merge request gitlab-org/gitlab-shell!214
parents c6577e0d 014691e0
source "http://rubygems.org" source "http://rubygems.org"
group :development, :test do group :development, :test do
gem 'guard', '~> 1.5.0' gem 'guard-rspec', '~> 4.0'
gem 'guard-rspec', '~> 2.1.0' gem 'listen', '~> 3.0.0'
gem 'listen', '~> 0.5.0' gem 'rspec', '~> 3.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', '~> 2.4.0' gem 'vcr', '~> 4.0'
gem 'webmock', '~> 1.9.0' gem 'webmock', '~> 1.9.0'
end end
...@@ -9,18 +9,32 @@ GEM ...@@ -9,18 +9,32 @@ 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)
guard (1.5.4) ffi (1.9.25)
listen (>= 0.4.2) formatador (0.2.5)
lumberjack (>= 1.0.2) guard (2.14.2)
pry (>= 0.9.10) formatador (>= 0.2.4)
thor (>= 0.14.6) listen (>= 2.7, < 4.0)
guard-rspec (2.1.2) lumberjack (>= 1.0.12, < 2.0)
guard (>= 1.1) nenv (~> 0.1)
rspec (~> 2.11) notiffany (~> 0.0)
listen (0.5.3) pry (>= 0.9.12)
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)
...@@ -32,17 +46,25 @@ GEM ...@@ -32,17 +46,25 @@ GEM
rainbow (2.2.2) rainbow (2.2.2)
rake rake
rake (12.3.1) rake (12.3.1)
rspec (2.99.0) rb-fsevent (0.10.3)
rspec-core (~> 2.99.0) rb-inotify (0.9.10)
rspec-expectations (~> 2.99.0) ffi (>= 0.5.0, < 2)
rspec-mocks (~> 2.99.0) rspec (3.7.0)
rspec-core (2.99.2) rspec-core (~> 3.7.0)
rspec-expectations (2.99.2) rspec-expectations (~> 3.7.0)
diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (~> 3.7.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 (2.99.4) rspec-mocks (3.7.0)
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)
...@@ -52,6 +74,7 @@ GEM ...@@ -52,6 +74,7 @@ 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)
...@@ -59,7 +82,7 @@ GEM ...@@ -59,7 +82,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 (2.4.0) vcr (4.0.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)
...@@ -68,14 +91,13 @@ PLATFORMS ...@@ -68,14 +91,13 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
guard (~> 1.5.0) guard-rspec (~> 4.0)
guard-rspec (~> 2.1.0) listen (~> 3.0.0)
listen (~> 0.5.0) rspec (~> 3.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 (~> 2.4.0) vcr (~> 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' do guard :rspec, cmd: 'bundle exec rspec' do
watch(%r{^spec/.+_spec\.rb$}) watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{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...
# #
key = ARGV[0] full_key = ARGV[0]
abort "# No key provided" if key.nil? || key.empty? abort "# No key provided" if full_key.nil? || full_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(key) authorized_key = GitlabNet.new.authorized_key(full_key)
if authorized_key.nil? if authorized_key.nil?
puts "# No key was found for #{key}" puts "# No key was found for #{full_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
...@@ -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 <username> <public-key> # bin/gitlab-shell-authorized-keys-check <expected-username> <actual-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
key = ARGV[2] full_key = ARGV[2]
abort "# No key provided" if key.nil? || key == '' abort "# No key provided" if full_key.nil? || full_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(key) authorized_key = GitlabNet.new.authorized_key(full_key)
if authorized_key.nil? if authorized_key.nil?
puts "# No key was found for #{key}" puts "# No key was found for #{full_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-keys-check <key-id> <principal1> [<principal2>...] # bin/gitlab-shell-authorized-principals-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 { |principal| principals.each do |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
key_id = ENV.delete('GL_ID') gl_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, key_id, refs).exec && if GitlabPostReceive.new(gl_repository, repo_path, gl_id, refs).exec &&
GitlabCustomHook.new(repo_path, key_id).post_receive(refs) GitlabCustomHook.new(repo_path, gl_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
key_id = ENV.delete('GL_ID') gl_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, key_id, refs, protocol).exec && if GitlabAccess.new(gl_repository, repo_path, gl_id, refs, protocol).exec &&
GitlabCustomHook.new(repo_path, key_id).pre_receive(refs) && GitlabCustomHook.new(repo_path, gl_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
key_id = ENV.delete('GL_ID') gl_id = ENV.delete('GL_ID')
require_relative '../lib/gitlab_custom_hook' require_relative '../lib/gitlab_custom_hook'
if GitlabCustomHook.new(repo_path, key_id).update(ref_name, old_value, new_value) if GitlabCustomHook.new(repo_path, gl_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
attr_reader :config, :gl_repository, :repo_path, :changes, :protocol def initialize(gl_repository, repo_path, gl_id, 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
@actor = actor @gl_id = gl_id
@changes = changes.lines @changes = changes.lines
@protocol = protocol @protocol = protocol
end end
def exec def exec
status = GitlabMetrics.measure('check-access:git-receive-pack') do 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"
...@@ -38,9 +32,19 @@ class GitlabAccess ...@@ -38,9 +32,19 @@ class GitlabAccess
false false
end end
protected private
attr_reader :gl_repository, :repo_path, :gl_id, :changes, :protocol
def api def api
GitlabNet.new @api ||= 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, key_id) def initialize(repo_path, gl_id)
@repo_path = repo_path @repo_path = repo_path
@vars = { 'GL_ID' => key_id } @vars = { 'GL_ID' => gl_id }
@config = GitlabConfig.new @config = GitlabConfig.new
end end
......
...@@ -9,6 +9,7 @@ class GitlabKeys # rubocop:disable Metrics/ClassLength ...@@ -9,6 +9,7 @@ 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
...@@ -21,6 +22,7 @@ class GitlabKeys # rubocop:disable Metrics/ClassLength ...@@ -21,6 +22,7 @@ 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 'gitlab_config' require_relative 'errors'
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 # rubocop:disable Metrics/ClassLength class GitlabNet
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, who, changes, protocol, env: {}) def check_access(cmd, gl_repository, repo, actor, changes, protocol = GL_PROTOCOL, env: {})
changes = changes.join("\n") unless changes.is_a?(String) changes = changes.join("\n") unless changes.is_a?(String)
params = { params = {
...@@ -29,56 +26,27 @@ class GitlabNet # rubocop:disable Metrics/ClassLength ...@@ -29,56 +26,27 @@ class GitlabNet # rubocop:disable Metrics/ClassLength
env: env env: env
} }
who_sym, _, who_v = self.class.parse_who(who) params[actor.identifier_key.to_sym] = actor.id
params[who_sym] = who_v
url = "#{internal_api_endpoint}/allowed" resp = post("#{internal_api_endpoint}/allowed", params)
resp = post(url, params)
if resp.code == '200' determine_action(actor, resp)
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(who) def discover(actor)
_, who_k, who_v = self.class.parse_who(who) resp = get("#{internal_api_endpoint}/discover?#{actor.identifier_key}=#{actor.id}")
JSON.parse(resp.body) if resp.code == HTTP_SUCCESS
resp = get("#{internal_api_endpoint}/discover?#{who_k}=#{who_v}") rescue JSON::ParserError, ApiUnreachableError
nil
JSON.parse(resp.body) rescue nil
end end
def lfs_authenticate(gl_id, repo) def lfs_authenticate(actor, repo)
id_sym, _, id = self.class.parse_who(gl_id) params = { project: sanitize_path(repo) }
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)
if resp.code == '200' GitlabLfsAuthentication.build_from_json(resp.body) if resp.code == HTTP_SUCCESS
GitlabLfsAuthentication.build_from_json(resp.body)
end
end end
def broadcast_message def broadcast_message
...@@ -93,11 +61,7 @@ class GitlabNet # rubocop:disable Metrics/ClassLength ...@@ -93,11 +61,7 @@ class GitlabNet # rubocop:disable Metrics/ClassLength
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)
if resp.code == '200' resp.code == HTTP_SUCCESS ? JSON.parse(resp.body) : []
JSON.parse(resp.body)
else
[]
end
rescue rescue
[] []
end end
...@@ -106,19 +70,17 @@ class GitlabNet # rubocop:disable Metrics/ClassLength ...@@ -106,19 +70,17 @@ class GitlabNet # rubocop:disable Metrics/ClassLength
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(key) def authorized_key(full_key)
resp = get("#{internal_api_endpoint}/authorized_keys?key=#{URI.escape(key, '+/=')}") resp = get("#{internal_api_endpoint}/authorized_keys?key=#{URI.escape(full_key, '+/=')}")
JSON.parse(resp.body) if resp.code == "200" JSON.parse(resp.body) if resp.code == HTTP_SUCCESS
rescue rescue
nil nil
end end
def two_factor_recovery_codes(gl_id) def two_factor_recovery_codes(actor)
id_sym, _, id = self.class.parse_who(gl_id) params = { actor.identifier_key.to_sym => actor.id }
resp = post("#{internal_api_endpoint}/two_factor_recovery_codes", params)
resp = post("#{internal_api_endpoint}/two_factor_recovery_codes", id_sym => id) JSON.parse(resp.body) if resp.code == HTTP_SUCCESS
JSON.parse(resp.body) if resp.code == '200'
rescue rescue
{} {}
end end
...@@ -127,51 +89,50 @@ class GitlabNet # rubocop:disable Metrics/ClassLength ...@@ -127,51 +89,50 @@ class GitlabNet # rubocop:disable Metrics/ClassLength
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 == '200' resp.code == HTTP_SUCCESS
rescue rescue
false false
end end
def post_receive(gl_repository, identifier, changes) def post_receive(gl_repository, actor, changes)
params = { params = { gl_repository: gl_repository, identifier: actor.identifier, changes: changes }
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
raise NotFound if resp.code == '404' JSON.parse(resp.body) if resp.code == HTTP_SUCCESS
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
raise NotFound if resp.code == '404' JSON.parse(resp.body) if resp.code == HTTP_SUCCESS
JSON.parse(resp.body) if resp.code == '200'
end
def self.parse_who(who)
if who.start_with?("key-")
value = who.gsub("key-", "")
raise ArgumentError, "who='#{who}' is invalid!" unless value =~ /\A[0-9]+\z/
[:key_id, 'key_id', value]
elsif who.start_with?("user-")
value = who.gsub("user-", "")
raise ArgumentError, "who='#{who}' is invalid!" unless value =~ /\A[0-9]+\z/
[:user_id, 'user_id', value]
elsif who.start_with?("username-")
[:username, 'username', who.gsub("username-", "")]
else
raise ArgumentError, "who='#{who}' is invalid!"
end
end end
protected private
def sanitize_path(repo) def sanitize_path(repo)
repo.delete("'") repo.delete("'")
end end
def determine_action(actor, resp)
json = JSON.parse(resp.body)
message = json['message']
case resp.code
when HTTP_SUCCESS
# TODO: This raise can be removed once internal API can respond with correct
# HTTP status codes, instead of relying upon parsing the body and
# accessing the 'status' key.
raise AccessDeniedError, message unless json['status']
Action::Gitaly.create_from_json(actor, json)
when HTTP_UNAUTHORIZED, HTTP_NOT_FOUND
raise AccessDeniedError, message
else
raise UnknownError, "#{API_INACCESSIBLE_ERROR}: #{message}"
end
rescue JSON::ParserError
raise UnknownError, API_INACCESSIBLE_ERROR
end
end end
...@@ -8,20 +8,18 @@ require 'securerandom' ...@@ -8,20 +8,18 @@ require 'securerandom'
class GitlabPostReceive class GitlabPostReceive
include NamesHelper include NamesHelper
attr_reader :config, :gl_repository, :repo_path, :changes, :jid def initialize(gl_repository, repo_path, gl_id, changes)
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
@actor = actor @gl_id = gl_id
@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
...@@ -35,12 +33,18 @@ class GitlabPostReceive ...@@ -35,12 +33,18 @@ class GitlabPostReceive
false false
end end
protected private
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
...@@ -100,8 +104,6 @@ class GitlabPostReceive ...@@ -100,8 +104,6 @@ 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,299 +3,120 @@ require 'pathname' ...@@ -3,299 +3,120 @@ require 'pathname'
require_relative 'gitlab_net' require_relative 'gitlab_net'
require_relative 'gitlab_metrics' require_relative 'gitlab_metrics'
require_relative 'actor'
class GitlabShell # rubocop:disable Metrics/ClassLength class GitlabShell
class AccessDeniedError < StandardError; end API_2FA_RECOVERY_CODES_COMMAND = '2fa_recovery_codes'.freeze
class DisallowedCommandError < StandardError; end
class InvalidRepositoryPathError < StandardError; end
GIT_COMMANDS = %w(git-upload-pack git-receive-pack git-upload-archive git-lfs-authenticate).freeze GIT_UPLOAD_PACK_COMMAND = 'git-upload-pack'.freeze
GITALY_MIGRATED_COMMANDS = { GIT_RECEIVE_PACK_COMMAND = 'git-receive-pack'.freeze
'git-upload-pack' => File.join(ROOT_PATH, 'bin', 'gitaly-upload-pack'), GIT_UPLOAD_ARCHIVE_COMMAND = 'git-upload-archive'.freeze
'git-upload-archive' => File.join(ROOT_PATH, 'bin', 'gitaly-upload-archive'), GIT_LFS_AUTHENTICATE_COMMAND = 'git-lfs-authenticate'.freeze
'git-receive-pack' => File.join(ROOT_PATH, 'bin', 'gitaly-receive-pack')
}.freeze
API_COMMANDS = %w(2fa_recovery_codes).freeze
GL_PROTOCOL = 'ssh'.freeze
attr_accessor :gl_id, :gl_repository, :repo_name, :command, :git_access, :git_protocol GIT_COMMANDS = [GIT_UPLOAD_PACK_COMMAND, GIT_RECEIVE_PACK_COMMAND,
attr_reader :repo_path GIT_UPLOAD_ARCHIVE_COMMAND, GIT_LFS_AUTHENTICATE_COMMAND].freeze
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)
unless origin_cmd if !origin_cmd || origin_cmd.empty?
puts "Welcome to GitLab, #{username}!" puts "Welcome to GitLab, #{actor.username}!"
return true return true
end end
args = Shellwords.shellwords(origin_cmd) parsed_command = parse_cmd(origin_cmd)
args = parse_cmd(args) action = determine_action(parsed_command)
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 => ex rescue AccessDeniedError, UnknownError => ex
$logger.warn('Access denied', command: origin_cmd, user: log_username) $logger.warn('Access denied', command: origin_cmd, user: actor.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: log_username) $logger.warn('Denied disallowed command', command: origin_cmd, user: actor.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
protected private
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 git_access_command = command
return args if API_COMMANDS.include?(@command) if command == API_2FA_RECOVERY_CODES_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]
case args[2] git_access_command = case args[2]
when 'download' when 'download'
@git_access = 'git-upload-pack' GIT_UPLOAD_PACK_COMMAND
when 'upload' when 'upload'
@git_access = 'git-receive-pack' GIT_RECEIVE_PACK_COMMAND
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
args
end
def verify_access
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
def process_cmd(args)
return send("api_#{@command}") if API_COMMANDS.include?(@command)
if @command == 'git-lfs-authenticate'
GitlabMetrics.measure('lfs-authenticate') do
$logger.info('Processing LFS authentication', user: log_username)
lfs_authenticate
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 end
if git_trace_available? Struct::ParsedCommand.new(command, git_access_command, repo_name, args)
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 api
GitlabNet.new
end end
def user def determine_action(parsed_command)
return @user if defined?(@user) return Action::API2FARecovery.new(actor) if parsed_command.command == API_2FA_RECOVERY_CODES_COMMAND
begin
if defined?(@who)
@user = api.discover(@who)
@gl_id = "user-#{@user['id']}"
else
@user = api.discover(@gl_id)
end
rescue GitlabNet::ApiUnreachableError
@user = nil
end
end
def username_from_discover
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 GitlabMetrics.measure('verify-access') do
lfs_access = api.lfs_authenticate(@gl_id, @repo_name) # GitlabNet#check_access will raise exception in the event of a problem
initial_action = api.check_access(
return unless lfs_access parsed_command.git_access_command,
nil,
puts lfs_access.authentication_payload parsed_command.repo_name,
end actor,
'_any'
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 case parsed_command.command
puts 'New recovery codes have *not* been generated. Existing codes will remain valid.' when GIT_LFS_AUTHENTICATE_COMMAND
return Action::GitLFSAuthenticate.new(actor, parsed_command.repo_name)
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 else
puts "An error occurred while trying to generate new recovery codes.\n" \ initial_action
"#{resp['message']}"
end end
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 end
begin def api
File.open(@config.git_trace_log_file, 'a') { nil } @api ||= GitlabNet.new
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
...@@ -76,7 +91,7 @@ module HTTPHelper ...@@ -76,7 +91,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 response.code == "200" if HTTP_SUCCESS_LIKE.include?(response.code)
$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)
...@@ -109,7 +124,7 @@ module HTTPHelper ...@@ -109,7 +124,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,31 +7,27 @@ describe GitlabAccess do ...@@ -7,31 +7,27 @@ 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|
api.stub(check_access: GitAccessStatus.new(true, allow(api).to receive(:check_access).and_return(
'ok', Action::Gitaly.new(
gl_repository: 'project-1', 'key-1',
gl_id: 'user-123', 'project-1',
gl_username: 'testuser', 'testuser',
repository_path: '/home/git/repositories', 'version=2',
gitaly: nil, '/home/git/repositories',
git_protocol: 'version=2')) nil
)
)
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|
access.stub(exec_cmd: :exec_called) allow(access).to receive(:exec_cmd).and_return(:exec_called)
access.stub(api: api) allow(access).to receive(:api).and_return(api)
end end
end end
before do before do
GitlabConfig.any_instance.stub(repos_path: repository_path) allow_any_instance_of(GitlabConfig).to receive(:repos_path).and_return(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
...@@ -43,16 +39,7 @@ describe GitlabAccess do ...@@ -43,16 +39,7 @@ describe GitlabAccess do
context "access is denied" do context "access is denied" do
before do before do
api.stub(check_access: GitAccessStatus.new( allow(api).to receive(:check_access).and_raise(AccessDeniedError)
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
...@@ -62,7 +49,7 @@ describe GitlabAccess do ...@@ -62,7 +49,7 @@ describe GitlabAccess do
context "API connection fails" do context "API connection fails" do
before do before do
api.stub(:check_access).and_raise(GitlabNet::ApiUnreachableError) allow(api).to receive(:check_access).and_raise(GitlabNet::ApiUnreachableError)
end end
it "returns false" do it "returns false" do
......
...@@ -3,29 +3,55 @@ require_relative '../lib/gitlab_config' ...@@ -3,29 +3,55 @@ 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.send(:config)['gitlab_url'] = url + '//' } before { config_data['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_format do describe '#log_level' 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 { gitlab_keys.key.should == 'ssh-rsa AAAAB3NzaDAxx2E' } it { expect(gitlab_keys.key).to eql 'ssh-rsa AAAAB3NzaDAxx2E' }
it { gitlab_keys.instance_variable_get(:@command).should == 'add-key' } it { expect(gitlab_keys.instance_variable_get(:@command)).to eql 'add-key' }
it { gitlab_keys.instance_variable_get(:@key_id).should == 'key-741' } it { expect(gitlab_keys.instance_variable_get(:@key_id)).to eql '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"
File.read(tmp_authorized_keys_path).should == "existing content\n#{auth_line}\n" expect(File.read(tmp_authorized_keys_path)).to eql "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
$logger.should_receive(:info).with("Adding key", {:key_id=>"key-741", :public_key=>"ssh-rsa AAAAB3NzaDAxx2E"}) expect($logger).to 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
gitlab_keys.send(:add_key).should be_truthy expect(gitlab_keys.send(:add_key)).to 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'
gitlab_keys.send(:list_keys).should == "#{auth_line1}\n" expect(gitlab_keys.send(:list_keys)).to eql "#{auth_line1}\n"
end end
end end
...@@ -118,10 +118,9 @@ describe GitlabKeys do ...@@ -118,10 +118,9 @@ describe GitlabKeys do
end end
it 'outputs the key IDs, separated by newlines' do it 'outputs the key IDs, separated by newlines' do
output = capture_stdout do expect do
gitlab_keys.send(:list_key_ids) gitlab_keys.send(:list_key_ids)
end end.to output("1\n2\n3\n9000\n").to_stdout
output.should match "1\n2\n3\n9000"
end end
end end
...@@ -130,38 +129,38 @@ describe GitlabKeys do ...@@ -130,38 +129,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
gitlab_keys.stub(stdin: fake_stdin) allow(gitlab_keys).to receive(:stdin).and_return(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"
File.read(tmp_authorized_keys_path).should == "existing content\n#{auth_line1}\n#{auth_line2}\n" expect(File.read(tmp_authorized_keys_path)).to eql "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
gitlab_keys.should_receive(:abort) expect(gitlab_keys).to 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
gitlab_keys.should_receive(:open).and_yield(double(:file, puts: nil, chmod: nil)) expect(gitlab_keys).to 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
$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-12', public_key: "ssh-dsa ASDFASGADG")
$logger.should_receive(:info).with("Adding key", key_id: 'key-123', public_key: "ssh-rsa GFDGDFSGSDFG") expect($logger).to 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
gitlab_keys.send(:batch_add_keys).should be_truthy expect(gitlab_keys.send(:batch_add_keys)).to be_truthy
end end
end end
end end
...@@ -187,22 +186,22 @@ describe GitlabKeys do ...@@ -187,22 +186,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(/./, '#')
File.read(tmp_authorized_keys_path).should == "existing content\n#{erased_line}\n#{other_line}\n" expect(File.read(tmp_authorized_keys_path)).to eql "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
gitlab_keys.stub(:open) allow(gitlab_keys).to receive(:open)
gitlab_keys.stub(:lock).and_yield allow(gitlab_keys).to receive(:lock).and_yield
end end
it "should log an rm-key event" do it "should log an rm-key event" do
$logger.should_receive(:info).with("Removing key", key_id: "key-741") expect($logger).to 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
gitlab_keys.send(:rm_key).should be_truthy expect(gitlab_keys.send(:rm_key)).to be_truthy
end end
end end
...@@ -219,7 +218,7 @@ describe GitlabKeys do ...@@ -219,7 +218,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(/./, '#')
File.read(tmp_authorized_keys_path).should == "existing content\n#{erased_line}\n#{other_line}\n" expect(File.read(tmp_authorized_keys_path)).to eql "existing content\n#{erased_line}\n#{other_line}\n"
end end
end end
end end
...@@ -228,8 +227,8 @@ describe GitlabKeys do ...@@ -228,8 +227,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
gitlab_keys.stub(:open) allow(gitlab_keys).to receive(:open)
gitlab_keys.send(:clear).should be_truthy expect(gitlab_keys.send(:clear)).to be_truthy
end end
end end
...@@ -242,7 +241,7 @@ describe GitlabKeys do ...@@ -242,7 +241,7 @@ describe GitlabKeys do
end end
it 'returns false if opening raises an exception' do it 'returns false if opening raises an exception' do
gitlab_keys.should_receive(:open_auth_file).and_raise("imaginary error") expect(gitlab_keys).to receive(:open_auth_file).and_raise("imaginary error")
expect(gitlab_keys.exec).to eq(false) expect(gitlab_keys.exec).to eq(false)
end end
...@@ -257,51 +256,51 @@ describe GitlabKeys do ...@@ -257,51 +256,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')
gitlab_keys.should_receive(:add_key) expect(gitlab_keys).to 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')
gitlab_keys.should_receive(:batch_add_keys) expect(gitlab_keys).to 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')
gitlab_keys.should_receive(:rm_key) expect(gitlab_keys).to 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')
gitlab_keys.should_receive(:clear) expect(gitlab_keys).to 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')
gitlab_keys.should_receive(:check_permissions) expect(gitlab_keys).to 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')
gitlab_keys.should_receive(:puts).with('not allowed') expect(gitlab_keys).to 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')
gitlab_keys.stub(puts: nil) allow(gitlab_keys).to receive(:puts).and_return(nil)
$logger.should_receive(:warn).with("Attempt to execute invalid gitlab-keys command", command: '"nooope"') expect($logger).to 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
GitlabKeys.any_instance.stub(lock_file: tmp_lock_file_path) allow_any_instance_of(GitlabKeys).to receive(:lock_file).and_return(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
...@@ -310,7 +309,7 @@ describe GitlabKeys do ...@@ -310,7 +309,7 @@ describe GitlabKeys do
key.send :lock, 1 do key.send :lock, 1 do
sleep 2 sleep 2
end end
end.to raise_error end.to raise_error(Timeout::Error)
end end
it "should actually lock file" do it "should actually lock file" do
...@@ -335,7 +334,7 @@ describe GitlabKeys do ...@@ -335,7 +334,7 @@ describe GitlabKeys do
end end
thr1.join thr1.join
$global.should == "foobar" expect($global).to eql "foobar"
end end
end end
...@@ -353,7 +352,7 @@ describe GitlabKeys do ...@@ -353,7 +352,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) }
gitlab_keys.stub(auth_file: tmp_authorized_keys_path) allow(gitlab_keys).to receive(:auth_file).and_return(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 { subject.username.should == 'dzaporozhets' } it { expect(subject.username).to eql 'dzaporozhets' }
it { subject.lfs_token.should == 'wsnys8Zm8Jn7zyhHTAAK' } it { expect(subject.lfs_token).to eql 'wsnys8Zm8Jn7zyhHTAAK' }
it { subject.repository_http_path.should == 'http://gitlab.dev/repo' } it { expect(subject.repository_http_path).to eql '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 { subject.authentication_payload.should eq(result) } it { expect(subject.authentication_payload).to 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)
json_payload['header']['Authorization'].should eq('Basic ZHphcG9yb3poZXRzOndzbnlzOFptOEpuN3p5aEhUQUFL') expect(json_payload['header']['Authorization']).to eq('Basic ZHphcG9yb3poZXRzOndzbnlzOFptOEpuN3p5aEhUQUFL')
json_payload['href'].should eq('http://gitlab.dev/repo/info/lfs/') expect(json_payload['href']).to 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
$stderr.should_receive(:puts).at_least(:once) expect($stderr).to 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 }
...@@ -8,57 +7,66 @@ describe GitlabNet, vcr: true do ...@@ -8,57 +7,66 @@ 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(:key2) { 'key-2' } let(:actor1) { Actor.new_from('key-1') }
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
gitlab_net.stub(:base_api_endpoint).and_return(base_api_endpoint) allow(gitlab_net).to receive(:base_api_endpoint).and_return(base_api_endpoint)
gitlab_net.stub(:secret_token).and_return(secret) allow(gitlab_net).to receive(: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
result.code.should == '200' expect(result.code).to eql('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
Net::HTTP::Get.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: secret)) allow_any_instance_of(Net::HTTP::Get).to 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
Net::HTTP.any_instance.stub(:request).and_raise(StandardError) allow_any_instance_of(Net::HTTP).to receive(:request).and_raise(StandardError)
expect { gitlab_net.check }.to raise_error(GitlabNet::ApiUnreachableError) expect do gitlab_net.check end.to raise_error(GitlabNet::ApiUnreachableError)
end end
end end
describe '#discover' do describe '#discover' do
it 'should return user has based on key id' do it 'returns user has based on key id' do
VCR.use_cassette("discover-ok") do VCR.use_cassette("discover-ok") do
user = gitlab_net.discover(key) user = gitlab_net.discover(actor1)
user['name'].should == 'Administrator' expect(user['name']).to eql 'Administrator'
user['username'].should == 'root' expect(user['username']).to eql '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
Net::HTTP::Get.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: secret)) allow_any_instance_of(Net::HTTP::Get).to receive(:set_form_data).with(hash_including(secret_token: secret))
gitlab_net.discover(key) gitlab_net.discover(actor1)
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
Net::HTTP.any_instance.stub(:request).and_raise(StandardError) allow_any_instance_of(Net::HTTP).to receive(:request).and_raise(StandardError)
expect { gitlab_net.discover(key) }.to raise_error(GitlabNet::ApiUnreachableError) expect(gitlab_net.discover(actor1)).to be_nil
end end
end end
end end
...@@ -67,10 +75,10 @@ describe GitlabNet, vcr: true do ...@@ -67,10 +75,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(key, project) lfs_access = gitlab_net.lfs_authenticate(actor1, project)
lfs_access.username.should == 'root' expect(lfs_access.username).to eql 'root'
lfs_access.lfs_token.should == 'Hyzhyde_wLUeyUQsR3tHGTG8eNocVQm4ssioTEsBSdb6KwCSzQ' expect(lfs_access.lfs_token).to eql 'Hyzhyde_wLUeyUQsR3tHGTG8eNocVQm4ssioTEsBSdb6KwCSzQ'
lfs_access.repository_http_path.should == URI.join(internal_api_endpoint.sub('api/v4', ''), project).to_s expect(lfs_access.repository_http_path).to eql URI.join(internal_api_endpoint.sub('api/v4', ''), project).to_s
end end
end end
end end
...@@ -81,7 +89,7 @@ describe GitlabNet, vcr: true do ...@@ -81,7 +89,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
result["message"].should == "Message" expect(result["message"]).to eql "Message"
end end
end end
end end
...@@ -90,7 +98,7 @@ describe GitlabNet, vcr: true do ...@@ -90,7 +98,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
result.should == {} expect(result).to eql({})
end end
end end
end end
...@@ -102,13 +110,13 @@ describe GitlabNet, vcr: true do ...@@ -102,13 +110,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
gitlab_net.should_receive(:get).with("#{internal_api_endpoint}/merge_request_urls?project=#{project}&changes=#{encoded_changes}&gl_repository=#{gl_repository}") expect(gitlab_net).to 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
gitlab_net.should_receive(:get).with("#{internal_api_endpoint}/merge_request_urls?project=#{project}&changes=#{encoded_changes}") expect(gitlab_net).to 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
...@@ -135,8 +143,7 @@ describe GitlabNet, vcr: true do ...@@ -135,8 +143,7 @@ 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
Net::HTTP::Post.any_instance.should_receive(:set_form_data) allow_any_instance_of(Net::HTTP::Post).to receive(:set_form_data).with(hash_including(params))
.with(hash_including(params))
VCR.use_cassette("pre-receive") { subject } VCR.use_cassette("pre-receive") { subject }
end end
...@@ -147,9 +154,9 @@ describe GitlabNet, vcr: true do ...@@ -147,9 +154,9 @@ describe GitlabNet, vcr: true do
end end
end end
it 'throws a NotFound error when pre-receive is not available' do it 'throws a NotFoundError when pre-receive is not available' do
VCR.use_cassette("pre-receive-not-found") do VCR.use_cassette("pre-receive-not-found") do
expect { subject }.to raise_error(GitlabNet::NotFound) expect do subject end.to raise_error(GitlabNet::NotFoundError)
end end
end end
end end
...@@ -158,7 +165,7 @@ describe GitlabNet, vcr: true do ...@@ -158,7 +165,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: key, changes: changes } { gl_repository: gl_repository, identifier: actor1.identifier, changes: changes }
end end
let(:merge_request_urls) do let(:merge_request_urls) do
[{ [{
...@@ -168,11 +175,10 @@ describe GitlabNet, vcr: true do ...@@ -168,11 +175,10 @@ describe GitlabNet, vcr: true do
}] }]
end end
subject { gitlab_net.post_receive(gl_repository, key, changes) } subject { gitlab_net.post_receive(gl_repository, actor1, changes) }
it 'sends the correct parameters' do it 'sends the correct parameters' do
Net::HTTP::Post.any_instance.should_receive(:set_form_data).with(hash_including(params)) allow_any_instance_of(Net::HTTP::Post).to receive(:set_form_data).with(hash_including(params))
VCR.use_cassette("post-receive") do VCR.use_cassette("post-receive") do
subject subject
...@@ -187,9 +193,9 @@ describe GitlabNet, vcr: true do ...@@ -187,9 +193,9 @@ describe GitlabNet, vcr: true do
end end
end end
it 'throws a NotFound error when post-receive is not available' do it 'throws a NotFoundError when post-receive is not available' do
VCR.use_cassette("post-receive-not-found") do VCR.use_cassette("post-receive-not-found") do
expect { subject }.to raise_error(GitlabNet::NotFound) expect do subject end.to raise_error(GitlabNet::NotFoundError)
end end
end end
end end
...@@ -200,21 +206,21 @@ describe GitlabNet, vcr: true do ...@@ -200,21 +206,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")
result.should be_nil expect(result).to 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")
result.should be_nil expect(result).to 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)
result.should eq({ expect(result).to eql({
"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,
...@@ -228,7 +234,7 @@ describe GitlabNet, vcr: true do ...@@ -228,7 +234,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(key) result = gitlab_net.two_factor_recovery_codes(actor1)
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
...@@ -236,7 +242,7 @@ describe GitlabNet, vcr: true do ...@@ -236,7 +242,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('key-777') result = gitlab_net.two_factor_recovery_codes(bad_actor1)
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
...@@ -252,7 +258,7 @@ describe GitlabNet, vcr: true do ...@@ -252,7 +258,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
Net::HTTP::Post.any_instance.should_receive(:set_form_data).with(hash_including(params)) allow_any_instance_of(Net::HTTP::Post).to 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
...@@ -265,92 +271,159 @@ describe GitlabNet, vcr: true do ...@@ -265,92 +271,159 @@ 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 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
access = gitlab_net.check_access('git-receive-pack', nil, project, key, changes, 'ssh') action = gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'ssh')
access.allowed?.should be_truthy expect(action).to be_instance_of(Action::Gitaly)
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
Net::HTTP::Post.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: secret)) allow_any_instance_of(Net::HTTP::Post).to receive(:set_form_data).with(hash_including(secret_token: secret))
gitlab_net.check_access('git-receive-pack', nil, project, key, changes, 'ssh') gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'ssh')
end end
end end
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
access = gitlab_net.check_access('git-upload-pack', nil, project, key, changes, 'ssh') action = gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'ssh')
access.allowed?.should be_truthy expect(action).to be_instance_of(Action::Gitaly)
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
access = gitlab_net.check_access('git-upload-pack', nil, project, key, changes, 'ssh') expect do
access.allowed?.should be_falsey gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'http')
access.message.should eq 'Git access over SSH is not allowed' 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-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
access = gitlab_net.check_access('git-receive-pack', nil, project, key, changes, 'ssh') expect do
access.allowed?.should be_falsey gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'ssh')
access.message.should eq 'Git access over SSH is not allowed' end.to raise_error(AccessDeniedError, '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
access = gitlab_net.check_access('git-upload-pack', nil, project, key, changes, 'http') expect do
access.allowed?.should be_falsey gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'http')
access.message.should eq 'Pulling over HTTP is not allowed.' end.to raise_error(AccessDeniedError, '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") do VCR.use_cassette('http-push-disabled-old') do
access = gitlab_net.check_access('git-receive-pack', nil, project, key, changes, 'http') expect do
access.allowed?.should be_falsey gitlab_net.check_access('git-receive-pack', nil, project, actor1, changes, 'http')
access.message.should eq 'Pushing over HTTP is not allowed.' end.to raise_error(AccessDeniedError, '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") do VCR.use_cassette('ssh-pull-project-denied-old') do
access = gitlab_net.check_access('git-receive-pack', nil, project, key2, changes, 'ssh') expect do
access.allowed?.should be_falsey 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
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") do VCR.use_cassette('ssh-push-project-denied-old') do
access = gitlab_net.check_access('git-upload-pack', nil, project, key2, changes, 'ssh') expect do
access.allowed?.should be_falsey 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
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") do VCR.use_cassette('ssh-push-project-denied-with-user-old') do
access = gitlab_net.check_access('git-upload-pack', nil, project, 'user-2', changes, 'ssh') expect do
access.allowed?.should be_falsey 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
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
Net::HTTP.any_instance.stub(:request).and_raise(StandardError) allow_any_instance_of(Net::HTTP).to receive(:request).and_raise(StandardError)
expect { expect {
gitlab_net.check_access('git-upload-pack', nil, project, 'user-1', changes, 'ssh') gitlab_net.check_access('git-upload-pack', nil, project, actor1, changes, 'ssh')
}.to raise_error(GitlabNet::ApiUnreachableError) }.to raise_error(GitlabNet::ApiUnreachableError)
end end
end end
...@@ -377,8 +450,8 @@ describe GitlabNet, vcr: true do ...@@ -377,8 +450,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
gitlab_net.stub :cert_store allow(gitlab_net).to receive(:cert_store)
gitlab_net.send(:config).stub(:http_settings) { {'self_signed_cert' => true} } allow(gitlab_net.send(:config)).to receive(:http_settings).and_return({ 'self_signed_cert' => true })
end end
its(:verify_mode) { should eq(OpenSSL::SSL::VERIFY_NONE) } its(:verify_mode) { should eq(OpenSSL::SSL::VERIFY_NONE) }
...@@ -398,11 +471,11 @@ describe GitlabNet, vcr: true do ...@@ -398,11 +471,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
gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('user').and_return(user)
gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('password').and_return(password)
Net::HTTP::Get.should_receive(:new).with('/', {}).and_return(get) expect(Net::HTTP::Get).to receive(:new).with('/', {}).and_return(get)
get.should_receive(:basic_auth).with(user, password).once expect(get).to receive(:basic_auth).with(user, password).once
get.should_receive(:set_form_data).with(hash_including(secret_token: secret)).once expect(get).to receive(:set_form_data).with(hash_including(secret_token: secret)).once
end end
it { should_not be_nil } it { should_not be_nil }
...@@ -412,11 +485,11 @@ describe GitlabNet, vcr: true do ...@@ -412,11 +485,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
gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('user').and_return(user)
gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('password').and_return(password)
Net::HTTP::Get.should_receive(:new).with('/', headers).and_return(get) expect(Net::HTTP::Get).to receive(:new).with('/', headers).and_return(get)
get.should_receive(:basic_auth).with(user, password).once expect(get).to receive(:basic_auth).with(user, password).once
get.should_receive(:set_form_data).with({ 'key1' => 'value1', secret_token: secret }).once expect(get).to receive(:set_form_data).with({ 'key1' => 'value1', secret_token: secret }).once
end end
it { should_not be_nil } it { should_not be_nil }
...@@ -426,11 +499,11 @@ describe GitlabNet, vcr: true do ...@@ -426,11 +499,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
gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('user').and_return(user)
gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('password').and_return(password)
Net::HTTP::Get.should_receive(:new).with('/', headers).and_return(get) expect(Net::HTTP::Get).to receive(:new).with('/', headers).and_return(get)
get.should_receive(:basic_auth).with(user, password).once expect(get).to receive(:basic_auth).with(user, password).once
get.should_receive(:set_form_data).with(hash_including(secret_token: secret)).once expect(get).to receive(:set_form_data).with(hash_including(secret_token: secret)).once
end end
it { should_not be_nil } it { should_not be_nil }
...@@ -441,12 +514,12 @@ describe GitlabNet, vcr: true do ...@@ -441,12 +514,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
gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('user').and_return(user)
gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('password').and_return(password)
Net::HTTP::Get.should_receive(:new).with('/', {}).and_return(get) expect(Net::HTTP::Get).to receive(:new).with('/', {}).and_return(get)
get.should_receive(:basic_auth).with(user, password).once expect(get).to receive(:basic_auth).with(user, password).once
get.should_receive(:body=).with({ 'key2' => 'value2', secret_token: secret }.to_json).once expect(get).to receive(:body=).with({ 'key2' => 'value2', secret_token: secret }.to_json).once
get.should_not_receive(:set_form_data) expect(get).to_not receive(:set_form_data)
end end
it { should_not be_nil } it { should_not be_nil }
...@@ -457,7 +530,7 @@ describe GitlabNet, vcr: true do ...@@ -457,7 +530,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
gitlab_net.should_receive(:secret_token).and_return(secret) expect(gitlab_net).to 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'))
...@@ -469,12 +542,12 @@ describe GitlabNet, vcr: true do ...@@ -469,12 +542,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|
OpenSSL::X509::Store.stub(:new) { store } allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
end end
end end
before :each do before :each do
store.should_receive(:set_default_paths).once expect(store).to receive(:set_default_paths).once
end end
after do after do
...@@ -482,17 +555,17 @@ describe GitlabNet, vcr: true do ...@@ -482,17 +555,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
gitlab_net.send(:config).http_settings.stub(:[]).with('ca_file') { 'test_file' } 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_path') { nil } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('ca_path').and_return(nil)
store.should_receive(:add_file).with('test_file') expect(store).to receive(:add_file).with('test_file')
store.should_not_receive(:add_path) expect(store).to_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
gitlab_net.send(:config).http_settings.stub(:[]).with('ca_file') { nil } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('ca_file').and_return(nil)
gitlab_net.send(:config).http_settings.stub(:[]).with('ca_path') { 'test_path' } allow(gitlab_net.send(:config).http_settings).to receive(:[]).with('ca_path').and_return('test_path')
store.should_not_receive(:add_file) expect(store).to_not receive(:add_file)
store.should_receive(:add_path).with('test_path') expect(store).to 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(:actor) { 'key-123' } let(:gl_id) { '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, actor, wrongly_encoded_changes) } let(:gitlab_post_receive) { GitlabPostReceive.new(gl_repository, repo_path, gl_id, 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
GitlabConfig.any_instance.stub(repos_path: repository_path) allow_any_instance_of(GitlabConfig).to receive(:repos_path).and_return(repository_path)
end end
describe "#exec" do describe "#exec" do
......
require_relative 'spec_helper' require_relative 'spec_helper'
require_relative '../lib/gitlab_shell' require_relative '../lib/gitlab_shell'
require_relative '../lib/gitlab_access_status' require_relative '../lib/action'
describe GitlabShell do describe GitlabShell do
before do before do
...@@ -8,565 +8,208 @@ describe GitlabShell do ...@@ -8,565 +8,208 @@ describe GitlabShell do
FileUtils.mkdir_p(tmp_repos_path) FileUtils.mkdir_p(tmp_repos_path)
end end
after do after { FileUtils.rm_rf(tmp_repos_path) }
FileUtils.rm_rf(tmp_repos_path)
end
subject do subject { described_class.new(who) }
ARGV[0] = gl_id
GitlabShell.new(gl_id).tap do |shell|
shell.stub(exec_cmd: :exec_called)
shell.stub(api: api)
end
end
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 let(:who) { 'key-1' }
double(GitlabNet).tap do |api| let(:audit_usernames) { true }
api.stub(discover: { 'name' => 'John Doe', 'username' => 'testuser' }) let(:actor) { Actor.new_from(who, audit_usernames: audit_usernames) }
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(:gl_id) { "key-#{rand(100) + 100}" }
let(:ssh_cmd) { nil }
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' }
before do let(:api) { double(GitlabNet) }
GitlabConfig.any_instance.stub(audit_usernames: false) let(:config) { double(GitlabConfig) }
end
describe :initialize do
let(:ssh_cmd) { 'git-receive-pack' }
its(:gl_id) { should == gl_id }
end
describe :parse_cmd do
describe 'git' do
context 'w/o namespace' do
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
context 'namespace' do
let(:repo_name) { 'dmitriy.zaporozhets/gitlab-ci.git' }
let(:ssh_args) { %w(git-upload-pack dmitriy.zaporozhets/gitlab-ci.git) }
before do let(:gitaly_action) { Action::Gitaly.new(
subject.send :parse_cmd, ssh_args actor,
end gl_repository,
gl_username,
its(:repo_name) { should == 'dmitriy.zaporozhets/gitlab-ci.git' } git_protocol,
its(:command) { should == 'git-upload-pack' } repo_path,
end { 'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default' } , 'address' => 'unix:gitaly.socket' })
}
context 'with an invalid number of arguments' do let(:api_2fa_recovery_action) { Action::API2FARecovery.new(actor) }
let(:ssh_args) { %w(foobar) } let(:git_lfs_authenticate_action) { Action::GitLFSAuthenticate.new(actor, repo_name) }
it "should raise an DisallowedCommandError" do
expect { subject.send :parse_cmd, ssh_args }.to raise_error(GitlabShell::DisallowedCommandError)
end
end
context 'with an API command' do
before do before do
subject.send :parse_cmd, ssh_args allow(GitlabConfig).to receive(:new).and_return(config)
end allow(config).to receive(:audit_usernames).and_return(audit_usernames)
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
describe 'git-lfs' do allow(Actor).to receive(:new_from).with(who, audit_usernames: audit_usernames).and_return(actor)
let(:repo_name) { 'dzaporozhets/gitlab.git' }
let(:ssh_args) { %w(git-lfs-authenticate dzaporozhets/gitlab.git download) }
before do allow(GitlabNet).to receive(:new).and_return(api)
subject.send :parse_cmd, ssh_args allow(api).to receive(:discover).with(actor).and_return('username' => gl_username)
end
its(:repo_name) { should == 'dzaporozhets/gitlab.git' }
its(:command) { should == 'git-lfs-authenticate' }
its(:git_access) { should == 'git-upload-pack' }
end end
describe 'git-lfs old clients' do describe '#exec' do
let(:repo_name) { 'dzaporozhets/gitlab.git' } context "when we don't have a valid user" do
let(:ssh_args) { %w(git-lfs-authenticate dzaporozhets/gitlab.git download long_oid) }
before do before do
subject.send :parse_cmd, ssh_args allow(api).to receive(:discover).with(actor).and_return(nil)
end end
its(:repo_name) { should == 'dzaporozhets/gitlab.git' } it 'prints Welcome.. and returns true' do
its(:command) { should == 'git-lfs-authenticate' } expect {
its(:git_access) { should == 'git-upload-pack' } expect(subject.exec(nil)).to be_truthy
end }.to output("Welcome to GitLab, Anonymous!\n").to_stdout
end end
describe :exec do
let(:gitaly_message) do
JSON.dump(
'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default' },
'gl_repository' => gl_repository,
'gl_id' => gl_id,
'gl_username' => gl_username,
'git_protocol' => git_protocol
)
end
before do
allow(ENV).to receive(:[]).with('GIT_PROTOCOL').and_return(git_protocol)
end end
shared_examples_for 'upload-pack' do |command| context 'when we have a valid user' do
let(:ssh_cmd) { "#{command} gitlab-ci.git" } context 'when origin_cmd is nil' do
after { subject.exec(ssh_cmd) } it 'prints Welcome.. and returns true' do
expect {
it "should process the command" do expect(subject.exec(nil)).to be_truthy
subject.should_receive(:process_cmd).with(%w(git-upload-pack gitlab-ci.git)) }.to output("Welcome to GitLab, @testuser!\n").to_stdout
end end
it "should execute the command" do
subject.should_receive(:exec_cmd).with('git-upload-pack', repo_path)
end end
it "should log the command execution" do context 'when origin_cmd is empty' do
message = "executing git command" it 'prints Welcome.. and returns true' do
user_string = "user with id #{gl_id}" expect {
$logger.should_receive(:info).with(message, command: "git-upload-pack #{repo_path}", user: user_string) expect(subject.exec('')).to be_truthy
}.to output("Welcome to GitLab, @testuser!\n").to_stdout
end 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 'git-upload-pack' do context 'when origin_cmd is invalid' do
it_behaves_like 'upload-pack', 'git-upload-pack' it 'prints a message to stderr and returns false' do
expect {
expect(subject.exec("git-invalid-command #{repo_name}")).to be_falsey
}.to output("GitLab: Disallowed command\n").to_stderr
end end
context 'git upload-pack' do
it_behaves_like 'upload-pack', 'git upload-pack'
end end
context 'gitaly-upload-pack' do context 'when origin_cmd is valid, but incomplete' do
let(:ssh_cmd) { "git-upload-pack gitlab-ci.git" } it 'prints a message to stderr and returns false' do
before do expect {
api.stub(check_access: gitaly_check_access) expect(subject.exec('git-upload-pack')).to be_falsey
}.to output("GitLab: Disallowed command\n").to_stderr
end 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 end
it "should execute the command" do context 'when origin_cmd is git-lfs-authenticate' do
subject.should_receive(:exec_cmd).with(File.join(ROOT_PATH, "bin/gitaly-upload-pack"), 'unix:gitaly.socket', gitaly_message) context 'but incomplete' do
it 'prints a message to stderr and returns false' do
expect {
expect(subject.exec('git-lfs-authenticate')).to be_falsey
}.to output("GitLab: Disallowed command\n").to_stderr
end 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 end
it "should use usernames if configured to do so" do context 'but invalid' do
GitlabConfig.any_instance.stub(audit_usernames: true) it 'prints a message to stderr and returns false' do
$logger.should_receive(:info).with("executing git command", hash_including(user: 'testuser')) expect {
expect(subject.exec("git-lfs-authenticate #{repo_name} invalid")).to be_falsey
}.to output("GitLab: Disallowed command\n").to_stderr
end end
end end
context 'git-receive-pack' do
let(:ssh_cmd) { "git-receive-pack gitlab-ci.git" }
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 end
it "should log the command execution" do context 'when origin_cmd is 2fa_recovery_codes' do
message = "executing git command" let(:origin_cmd) { '2fa_recovery_codes' }
user_string = "user with id #{gl_id}" let(:git_access) { '2fa_recovery_codes' }
$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
api.stub(check_access: gitaly_check_access) expect(Action::API2FARecovery).to receive(:new).with(actor).and_return(api_2fa_recovery_action)
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
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: exec_cmd_log_params.join(' '), user: user_string)
end end
it "should use usernames if configured to do so" do it 'returns true' do
GitlabConfig.any_instance.stub(audit_usernames: true) expect(api_2fa_recovery_action).to receive(:execute).with('2fa_recovery_codes', %w{ 2fa_recovery_codes }).and_return(true)
$logger.should_receive(:info).with("executing git command", hash_including(user: 'testuser')) expect(subject.exec(origin_cmd)).to be_truthy
end end
end end
context 'git-upload-archive' do context 'when access to the repo is denied' 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
api.stub(check_access: gitaly_check_access) expect(api).to receive(:check_access).with('git-upload-pack', nil, repo_name, actor, '_any').and_raise(AccessDeniedError, 'Sorry, access denied')
end
it_behaves_like 'upload-archive', 'git-upload-archive' do
let(:gitaly_executable) { "gitaly-upload-archive" }
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
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 end
context 'no command' do it 'prints a message to stderr and returns false' do
after { subject.exec(nil) } expect($stderr).to receive(:puts).with('GitLab: Sorry, access denied')
expect(subject.exec("git-upload-pack #{repo_name}")).to be_falsey
it "should call api.discover" do
api.should_receive(:discover).with(gl_id)
end end
end end
context "failed connection" do context 'when the API is unavailable' do
let(:ssh_cmd) { 'git-upload-pack gitlab-ci.git' }
before do before do
api.stub(:check_access).and_raise(GitlabNet::ApiUnreachableError) expect(api).to receive(:check_access).with('git-upload-pack', nil, repo_name, actor, '_any').and_raise(GitlabNet::ApiUnreachableError)
end end
after { subject.exec(ssh_cmd) }
it "should not process the command" do it 'prints a message to stderr and returns false' do
subject.should_not_receive(:process_cmd) expect($stderr).to receive(:puts).with('GitLab: Failed to authorize your Git request: internal API unreachable')
end expect(subject.exec("git-upload-pack #{repo_name}")).to be_falsey
it "should not execute the command" do
subject.should_not_receive(:exec_cmd)
end end
end end
context 'with an API command' do context 'when access has been verified OK' do
before do before do
allow(subject).to receive(:continue?).and_return(true) expect(api).to receive(:check_access).with(git_access, nil, repo_name, actor, '_any').and_return(gitaly_action)
end
context 'when generating recovery codes' do
let(:ssh_cmd) { '2fa_recovery_codes' }
after do
subject.exec(ssh_cmd)
end
it 'does not call verify_access' do
expect(subject).not_to receive(:verify_access)
end
it 'calls the corresponding method' do
expect(subject).to receive(:api_2fa_recovery_codes)
end
it 'outputs recovery codes' do
expect($stdout).to receive(:puts)
.with(/f67c514de60c4953\n41278385fc00c1e0/)
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
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
it "should disallow access and log the attempt if check_access returns false status" do
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))
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 } context 'when origin_cmd is git-upload-pack' do
end let(:origin_cmd) { 'git-upload-pack' }
let(:git_access) { 'git-upload-pack' }
context "with a path that doesn't match an absolute path" do it 'returns true' do
before do expect(gitaly_action).to receive(:execute).with('git-upload-pack', %W{git-upload-pack #{repo_name}}).and_return(true)
File.stub(:absolute_path) { 'y/gitlab-ci.git' } expect(subject.exec("#{origin_cmd} #{repo_name}")).to be_truthy
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
describe :exec_cmd do context 'but repo path is invalid' do
let(:shell) { GitlabShell.new(gl_id) } it 'prints a message to stderr and returns false' do
let(:env) do 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')
'HOME' => ENV['HOME'], expect(subject.exec("#{origin_cmd} #{repo_name}")).to be_falsey
'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 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 end
it "uses Kernel::exec method" do context "but we're using an old git version for Windows 2.14" do
Kernel.should_receive(:exec).with(env, 1, 2, exec_options).once it 'returns true' do
shell.send :exec_cmd, 1, 2 expect(gitaly_action).to receive(:execute).with('git-upload-pack', %W{git-upload-pack #{repo_name}}).and_return(true)
expect(subject.exec("git upload-pack #{repo_name}")).to be_truthy #NOTE: 'git upload-pack' vs. 'git-upload-pack'
end end
it "refuses to execute a lone non-array argument" do
expect { shell.send :exec_cmd, 1 }.to raise_error(GitlabShell::DisallowedCommandError)
end 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 end
context "when specifying a git_tracing log file" do context 'when origin_cmd is git-lfs-authenticate' do
let(:git_trace_log_file) { '/tmp/git_trace_performance.log' } let(:origin_cmd) { 'git-lfs-authenticate' }
let(:lfs_access) { double(GitlabLfsAuthentication, authentication_payload: fake_payload)}
before do before do
GitlabConfig.any_instance.stub(git_trace_log_file: git_trace_log_file) expect(Action::GitLFSAuthenticate).to receive(:new).with(actor, repo_name).and_return(git_lfs_authenticate_action)
shell
end end
it "uses GIT_TRACE_PERFORMANCE" do context 'upload' do
expected_hash = hash_including( let(:git_access) { 'git-receive-pack' }
'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] it 'returns true' do
expect(git_lfs_authenticate_action).to receive(:execute).with('git-lfs-authenticate', %w{ git-lfs-authenticate gitlab-ci.git upload }).and_return(true)
expect(subject.exec("#{origin_cmd} #{repo_name} upload")).to be_truthy
end 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
it "writes an entry on the log" do context 'download' do
message = 'git trace log path must be absolute, ignoring' let(:git_access) { 'git-upload-pack' }
expect($logger).to receive(:warn).
with(message, git_trace_log_file: git_trace_log_file)
Kernel.should_receive(:exec).with(env, [1, 2], exec_options).once
shell.send :exec_cmd, [1, 2]
end
end
context "when provides a file not writable" do it 'returns true' do
before do expect(git_lfs_authenticate_action).to receive(:execute).with('git-lfs-authenticate', %w{ git-lfs-authenticate gitlab-ci.git download }).and_return(true)
expect(File).to receive(:open).with(git_trace_log_file, 'a').and_raise(Errno::EACCES) expect(subject.exec("#{origin_cmd} #{repo_name} download")).to be_truthy
end end
it "does not uses GIT_TRACE*" do context 'for old git-lfs clients' do
# If we try to use it we'll show a warning to the users it 'returns true' do
expected_hash = hash_excluding( 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)
'GIT_TRACE', 'GIT_TRACE_PACKET', 'GIT_TRACE_PERFORMANCE' expect(subject.exec("#{origin_cmd} #{repo_name} download long_oid")).to be_truthy
)
Kernel.should_receive(:exec).with(expected_hash, [1, 2], exec_options).once
shell.send :exec_cmd, [1, 2]
end 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 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 { extract_ref_name('refs/heads/awesome-feature').should == 'awesome-feature' } it { expect(extract_ref_name('refs/heads/awesome-feature')).to eql 'awesome-feature' }
it { extract_ref_name('refs/tags/v2.2.1').should == 'v2.2.1' } it { expect(extract_ref_name('refs/tags/v2.2.1')).to eql 'v2.2.1' }
it { extract_ref_name('refs/tags/releases/v2.2.1').should == 'releases/v2.2.1' } it { expect(extract_ref_name('refs/tags/releases/v2.2.1')).to eql '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-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
......
...@@ -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
......
---
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: 200 code: 401
message: OK message: Unauthorized
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: 200 code: 401
message: OK message: Unauthorized
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: 200 code: 401
message: OK message: Unauthorized
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: 200 code: 401
message: OK message: Unauthorized
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: 200 code: 401
message: OK message: Unauthorized
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: 200 code: 401
message: OK message: Unauthorized
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: 200 code: 401
message: OK message: Unauthorized
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