diff options
44 files changed, 1282 insertions, 422 deletions
@@ -1,3 +1,10 @@ config.yml tmp/* *.log +/*.log* +authorized_keys.lock +coverage/ +.gitlab_shell_secret +.bundle +tags +.bundle/ diff --git a/.hound.yml b/.hound.yml new file mode 100644 index 0000000..4799e9d --- /dev/null +++ b/.hound.yml @@ -0,0 +1,3 @@ +StringLiterals: + EnforcedStyle: single_quotes + Enabled: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0b677af..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: ruby -env: - - TRAVIS=true -branches: - only: - - 'master' -rvm: - - 1.9.3-p327 - - 2.0.0 -before_script: - - "cp config.yml.example config.yml" -script: "bundle exec rspec spec" - @@ -1,3 +1,91 @@ +v2.1.0 + - Use secret token with GitLab internal API. Requires GitLab 7.5 or higher + +v2.0.1 + - Send post-receive changes to redis as a string instead of array + +v2.0.0 + - Works with GitLab v7.3+ + - Replace raise with abort when checking path to prevent path exposure + - Handle invalid number of arguments on remote commands + - Replace update hook with pre-receive and post-receive hooks. + - Symlink the whole hooks directory + - Ignore missing repositories in create-hooks + - Connect to Redis via sockets by default + +v1.9.7 + - Increased test coverage + - By default use direct unicorn connection (localhost:8080) + - Fix wrong repo path send to GitLab by GitlabUpdate hook + +v1.9.6 + - Explicitly require 'timeout' from the standard library + +v1.9.5 + - Put authorized_keys.lock in the same directory as authorized_keys + - Use lock file when add new entries to authorized_keys + +v1.9.4 + - Use lock file when modify authorized_keys + +v1.9.3 + - Ignore force push detection for new branch or branch remove push + +v1.9.2 + - Add support for force push detection + +v1.9.1 + - Update hook sends branch and tag name + +v1.9.0 + - Call api in update hook for both ssdh and http push. Requires GitLab 6.7+ + - Pass oldrev and newrev to api.allowed? + +v1.8.5 + - Add `gitlab-keys batch-add-keys` subcommand for authorized_keys rebuilds + +v1.8.4 + - Dont do import if repository exists + +v1.8.3 + - Add timeout option for repository import + +v1.8.2 + - Fix broken 1.8.1 + +v1.8.1 + - Restrict Environment Variables + - Add bin/create-hooks command + - More safe shell execution + +v1.8.0 + - Fix return values in GitlabKeys + +v1.7.9 + - Fix escape of repository path for custom ssh port + +v1.7.8 + - Escape repository path to prevent relative links (CVE-2013-4583) + +v1.7.7 + - Separate options from arguments with -- (CVE-2013-4582) + - Bypass shell and use stdlib JSON for GitlabUpdate (CVE-2013-4581) + +v1.7.6 + - Fix gitlab-projects update-head for improted repo when branch exists but not listed in refs/head + +v1.7.5 + - Remove keys from authorized_keys using ruby instead of shell + +v1.7.4 + - More protection against shell injection (CVE-2013-4546) + +v1.7.3 + - Use Kernel#open to append lines to authorized_keys (CVE-2013-4490) + +v1.7.2 + - More safe command execution + v1.7.1 - Fixed issue when developers are able to push to protected branches that contain a '/' in the branch name. @@ -1,6 +1,6 @@ source "http://rubygems.org" -group :development do +group :development, :test do gem 'coveralls', require: false gem 'rspec' gem 'webmock' diff --git a/Gemfile.lock b/Gemfile.lock index 338ee07..661d576 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,15 +3,15 @@ GEM specs: addressable (2.3.2) coderay (1.0.8) - colorize (0.5.8) - coveralls (0.6.2) - colorize + coveralls (0.7.1) multi_json (~> 1.3) rest-client simplecov (>= 0.7) + term-ansicolor thor crack (0.3.1) diff-lcs (1.1.3) + docile (1.1.5) guard (1.5.4) listen (>= 0.4.2) lumberjack (>= 1.0.2) @@ -23,14 +23,16 @@ GEM listen (0.5.3) lumberjack (1.0.2) method_source (0.8.1) - mime-types (1.21) - multi_json (1.6.1) + mime-types (2.3) + multi_json (1.10.1) + netrc (0.7.7) pry (0.9.10) coderay (~> 1.0.5) method_source (~> 0.8) slop (~> 3.3.1) - rest-client (1.6.7) - mime-types (>= 1.16) + rest-client (1.7.2) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) rspec (2.12.0) rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) @@ -39,12 +41,16 @@ GEM rspec-expectations (2.12.1) diff-lcs (~> 1.1.3) rspec-mocks (2.12.2) - simplecov (0.7.1) + simplecov (0.9.1) + docile (~> 1.1.0) multi_json (~> 1.0) - simplecov-html (~> 0.7.1) - simplecov-html (0.7.1) + simplecov-html (~> 0.8.0) + simplecov-html (0.8.0) slop (3.3.3) - thor (0.16.0) + term-ansicolor (1.3.0) + tins (~> 1.0) + thor (0.19.1) + tins (1.3.3) vcr (2.4.0) webmock (1.9.0) addressable (>= 2.2.7) @@ -1,78 +1,88 @@ -### gitlab-shell: ssh access and repository management +# gitlab-shell: ssh access and repository management -#### Code status +GitLab Shell is an application that allows you to execute git commands +and provide ssh access to git repositories. +It is not a Unix shell nor a replacement for Bash or Zsh. -* [](http://ci.gitlab.org/projects/4?ref=master) -* [](https://travis-ci.org/gitlabhq/gitlab-shell) -* [](https://codeclimate.com/github/gitlabhq/gitlab-shell) -* [](https://coveralls.io/r/gitlabhq/gitlab-shell) +## Code status +[](http://ci.gitlab.org/projects/4?ref=master) +[](https://semaphoreapp.com/gitlabhq/gitlab-shell) +[](https://codeclimate.com/github/gitlabhq/gitlab-shell) +[](https://coveralls.io/r/gitlabhq/gitlab-shell) -__Requires ruby 1.9+__ +**Requires ruby 1.9+** - -### Setup +## Setup ./bin/install - -### Check +## Check ./bin/check +## Repos -### Repos: - - -Add repo +Add repo: ./bin/gitlab-projects add-project gitlab/gitlab-ci.git -Remove repo +Remove repo: ./bin/gitlab-projects rm-project gitlab/gitlab-ci.git -Import repo +List repos: + + ./bin/gitlab-projects list-projects + +Import repo: + + # Default timeout is 2 minutes + ./bin/gitlab-projects import-project randx/six.git https://github.com/randx/six.git - ./bin/gitlab-projects import-project randx/six.git https://github.com/randx/six.git + # Override timeout in seconds + ./bin/gitlab-projects import-project randx/six.git https://github.com/randx/six.git 90 -Fork repo +Fork repo: ./bin/gitlab-projects fork-project gitlab/gitlab-ci.git randx -Update HEAD +Update HEAD: ./bin/gitlab-projects update-head gitlab/gitlab-ci.git 3-2-stable -Create branch +Create branch: ./bin/gitlab-projects create-branch gitlab/gitlab-ci.git 3-2-stable master -Remove branch +Remove branch: ./bin/gitlab-projects rm-branch gitlab/gitlab-ci.git 3-0-stable -Create tag +Create tag (lightweight & annotated): ./bin/gitlab-projects create-tag gitlab/gitlab-ci.git v3.0.0 3-0-stable + ./bin/gitlab-projects create-tag gitlab/gitlab-ci.git v3.0.0 3-0-stable 'annotated message goes here' -Remove tag +Remove tag: ./bin/gitlab-projects rm-tag gitlab/gitlab-ci.git v3.0.0 +## Keys -### Keys: - - -Add key +Add key: ./bin/gitlab-keys add-key key-782 "ssh-rsa AAAAx321..." -Remove key +Remove key: ./bin/gitlab-keys rm-key key-23 "ssh-rsa AAAAx321..." -Remove all keys from authorized_keys file +List all keys: - ./bin/gitlab-keys clear + ./bin/gitlab-keys list-keys + +Remove all keys from authorized_keys file: + + ./bin/gitlab-keys clear @@ -1 +1 @@ -1.7.1 +2.1.0 @@ -12,7 +12,7 @@ resp = GitlabNet.new.check if resp.code == "200" print 'OK' else - puts "FAILED. code: #{resp.code}" + abort "FAILED. code: #{resp.code}" end puts "\nCheck directories and files: " @@ -21,11 +21,18 @@ config = GitlabConfig.new dirs = [config.repos_path, config.auth_file] dirs.each do |dir| + abort("ERROR: missing option in config.yml") unless dir print "\t#{dir}: " if File.exists?(dir) print 'OK' else - puts "FAILED" + abort "FAILED" end puts "\n" end + +print "Test redis-cli executable: " +abort('FAILED') unless system(*config.redis_command, '--version') + +print "Send ping to redis server: " +abort unless system(*config.redis_command, 'ping') diff --git a/bin/create-hooks b/bin/create-hooks new file mode 100755 index 0000000..4efa650 --- /dev/null +++ b/bin/create-hooks @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +# Recreate GitLab hooks in the Git repositories managed by GitLab. +# +# This script is used when restoring a GitLab backup. + +require_relative '../lib/gitlab_init' +require File.join(ROOT_PATH, 'lib', 'gitlab_projects') + +Dir["#{GitlabConfig.new.repos_path}/*/*.git"].each do |repo| + begin + GitlabProjects.create_hooks(repo) + rescue Errno::ENOENT + # The user must have deleted their repository. Ignore. + end +end diff --git a/bin/gitlab-keys b/bin/gitlab-keys index d402d1e..9eb1950 100755 --- a/bin/gitlab-keys +++ b/bin/gitlab-keys @@ -8,9 +8,13 @@ require_relative '../lib/gitlab_init' # Ex. # /bin/gitlab-keys add-key key-782 "ssh-rsa AAAAx321..." # +# printf "key-782\tssh-rsa AAAAx321...\n" | /bin/gitlab-keys batch-add-keys +# # /bin/gitlab-keys rm-key key-23 "ssh-rsa AAAAx321..." # -# /bin/gitlab-keys clear" +# /bin/gitlab-keys list-keys +# +# /bin/gitlab-keys clear # require File.join(ROOT_PATH, 'lib', 'gitlab_keys') diff --git a/bin/gitlab-projects b/bin/gitlab-projects index 01de20b..c354fe5 100755 --- a/bin/gitlab-projects +++ b/bin/gitlab-projects @@ -11,6 +11,8 @@ require_relative '../lib/gitlab_init' # # /bin/gitlab-projects rm-project gitlab/gitlab-ci.git # +# /bin/gitlab-projects list-projects +# # /bin/gitlab-projects mv-project gitlab/gitlab-ci.git randx/fork.git # # /bin/gitlab-projects fork-project gitlab/gitlab-ci.git randx diff --git a/bin/install b/bin/install index f8c12f8..64ae726 100755 --- a/bin/install +++ b/bin/install @@ -10,17 +10,23 @@ config = GitlabConfig.new key_dir = File.dirname("#{config.auth_file}") commands = [ - "mkdir -p #{config.repos_path}", - "mkdir -p #{key_dir}", - "chmod 700 #{key_dir}", - "touch #{config.auth_file}", - "chmod 600 #{config.auth_file}", - "chmod -R ug+rwX,o-rwx #{config.repos_path}", - "find #{config.repos_path} -type d -print0 | xargs -0 chmod g+s" + %W(mkdir -p #{config.repos_path}), + %W(mkdir -p #{key_dir}), + %W(chmod 700 #{key_dir}), + %W(touch #{config.auth_file}), + %W(chmod 600 #{config.auth_file}), + %W(chmod -R ug+rwX,o-rwx #{config.repos_path}), + %W(find #{config.repos_path} -type d -exec chmod g+s {} ;) ] commands.each do |cmd| - puts "#{cmd}: #{system(cmd)}" + print "#{cmd.join(' ')}: " + if system(*cmd) + puts 'OK' + else + puts 'Failed' + abort "#{$PROGRAM_NAME} failed" + end end exit diff --git a/config.yml.example b/config.yml.example index 6922b48..97b5006 100644 --- a/config.yml.example +++ b/config.yml.example @@ -2,8 +2,13 @@ user: git # Url to gitlab instance. Used for api calls. Should end with a slash. -gitlab_url: "http://localhost/" +# Default: http://localhost:8080/ +# You only have to change the default if you have configured Unicorn +# to listen on a custom port, or if you have configured Unicorn to +# only listen on a Unix domain socket. +gitlab_url: "http://localhost:8080/" +# See installation.md#using-https for additional HTTPS configuration details. http_settings: # user: someone # password: somepass @@ -12,7 +17,9 @@ http_settings: self_signed_cert: false # Repositories path -# REPOS_PATH MUST NOT BE A SYMLINK!!! +# Give the canonicalized absolute pathname, +# REPOS_PATH MUST NOT CONTAIN ANY SYMLINK!!! +# Check twice that none of the components is a symlink, including "/home". repos_path: "/home/git/repositories" # File used as authorized_keys for gitlab user @@ -21,9 +28,11 @@ auth_file: "/home/git/.ssh/authorized_keys" # Redis settings used for pushing commit notices to gitlab redis: bin: /usr/bin/redis-cli - host: 127.0.0.1 - port: 6379 - # socket: /tmp/redis.socket # Only define this if you want to use sockets + # host: 127.0.0.1 + # port: 6379 + # pass: redispass # Allows you to specify the password for Redis + database: 0 + socket: /var/run/redis/redis.sock # Comment out this line if you want to use TCP namespace: resque:gitlab # Log file. diff --git a/hooks/post-receive b/hooks/post-receive new file mode 100755 index 0000000..d85ad42 --- /dev/null +++ b/hooks/post-receive @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# This file was placed here by GitLab. It makes sure that your pushed commits +# will be processed properly. +# You can add your own hooks to this file, but be careful when updating gitlab-shell! + +changes = ARGF.read +key_id = ENV['GL_ID'] +repo_path = Dir.pwd + +require_relative '../lib/gitlab_post_receive' + +if GitlabPostReceive.new(repo_path, key_id, changes).exec + exit 0 +else + exit 1 +end diff --git a/hooks/update b/hooks/pre-receive index 549afeb..2b979fa 100755 --- a/hooks/update +++ b/hooks/pre-receive @@ -4,10 +4,14 @@ # will be processed properly. # You can add your own hooks to this file, but be careful when updating gitlab-shell! -refname = ARGV[0] +refs = ARGF.read key_id = ENV['GL_ID'] -repo_path = `pwd` +repo_path = Dir.pwd -require_relative '../lib/gitlab_update' +require_relative '../lib/gitlab_access' -GitlabUpdate.new(repo_path, key_id, refname).exec +if GitlabAccess.new(repo_path, key_id, refs).exec + exit 0 +else + exit 1 +end diff --git a/lib/gitlab_access.rb b/lib/gitlab_access.rb new file mode 100644 index 0000000..80c050c --- /dev/null +++ b/lib/gitlab_access.rb @@ -0,0 +1,35 @@ +require_relative 'gitlab_init' +require_relative 'gitlab_net' +require_relative 'names_helper' +require 'json' + +class GitlabAccess + include NamesHelper + + attr_reader :config, :repo_path, :repo_name, :changes + + def initialize(repo_path, actor, changes) + @config = GitlabConfig.new + @repo_path = repo_path.strip + @actor = actor + @repo_name = extract_repo_name(@repo_path.dup, config.repos_path.to_s) + @changes = changes.lines + end + + def exec + if api.allowed?('git-receive-pack', @repo_name, @actor, @changes) + exit 0 + else + # reset GL_ID env since we stop git push here + ENV['GL_ID'] = nil + puts "GitLab: You are not allowed to access some of the refs!" + exit 1 + end + end + + protected + + def api + GitlabNet.new + end +end diff --git a/lib/gitlab_config.rb b/lib/gitlab_config.rb index 9dc5c66..c97743b 100644 --- a/lib/gitlab_config.rb +++ b/lib/gitlab_config.rb @@ -7,12 +7,16 @@ class GitlabConfig @config = YAML.load_file(File.join(ROOT_PATH, 'config.yml')) end + def home + ENV['HOME'] + end + def repos_path - @config['repos_path'] ||= "/home/git/repositories" + @config['repos_path'] ||= File.join(home, "repositories") end def auth_file - @config['auth_file'] ||= "/home/git/.ssh/authorized_keys" + @config['auth_file'] ||= File.join(home, ".ssh/authorized_keys") end def gitlab_url @@ -48,12 +52,19 @@ class GitlabConfig if redis.empty? # Default to old method of connecting to redis # for users that haven't updated their configuration - "env -i redis-cli" + %W(env -i redis-cli) else + redis['database'] ||= 0 + redis['host'] ||= '127.0.0.1' + redis['port'] ||= '6379' if redis.has_key?("socket") - "#{redis['bin']} -s #{redis['socket']}" + %W(#{redis['bin']} -s #{redis['socket']} -n #{redis['database']}) else - "#{redis['bin']} -h #{redis['host']} -p #{redis['port']}" + if redis.has_key?("pass") + %W(#{redis['bin']} -h #{redis['host']} -p #{redis['port']} -n #{redis['database']} -a #{redis['pass']}) + else + %W(#{redis['bin']} -h #{redis['host']} -p #{redis['port']} -n #{redis['database']}) + end end end end diff --git a/lib/gitlab_keys.rb b/lib/gitlab_keys.rb index dc54740..100e164 100644 --- a/lib/gitlab_keys.rb +++ b/lib/gitlab_keys.rb @@ -1,4 +1,5 @@ require 'tempfile' +require 'timeout' require_relative 'gitlab_config' require_relative 'gitlab_logger' @@ -16,7 +17,9 @@ class GitlabKeys def exec case @command when 'add-key'; add_key + when 'batch-add-keys'; batch_add_keys when 'rm-key'; rm_key + when 'list-keys'; puts list_keys when 'clear'; clear else $logger.warn "Attempt to execute invalid gitlab-keys command #{@command.inspect}." @@ -28,21 +31,84 @@ class GitlabKeys protected def add_key - $logger.info "Adding key #{@key_id} => #{@key.inspect}" - cmd = "command=\"#{ROOT_PATH}/bin/gitlab-shell #{@key_id}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{@key}" - cmd = "echo \'#{cmd}\' >> #{auth_file}" - system(cmd) + lock do + $logger.info "Adding key #{@key_id} => #{@key.inspect}" + auth_line = key_line(@key_id, @key) + open(auth_file, 'a') { |file| file.puts(auth_line) } + end + true + end + + def list_keys + $logger.info 'Listing all keys' + keys = '' + File.readlines(auth_file).each do |line| + # key_id & public_key + # command=".../bin/gitlab-shell key-741" ... ssh-rsa AAAAB3NzaDAxx2E\n + # ^^^^^^^ ^^^^^^^^^^^^^^^ + matches = /^command=\".+?\s+(.+?)\".+?ssh-rsa\s(.+)\s*.*\n*$/.match(line) + keys << "#{matches[1]} #{matches[2]}\n" unless matches.nil? + end + keys + end + + def batch_add_keys + lock do + open(auth_file, 'a') do |file| + stdin.each_line do |input| + tokens = input.strip.split("\t") + abort("#{$0}: invalid input #{input.inspect}") unless tokens.count == 2 + key_id, public_key = tokens + $logger.info "Adding key #{key_id} => #{public_key.inspect}" + file.puts(key_line(key_id, public_key)) + end + end + end + true + end + + def stdin + $stdin + end + + def key_line(key_id, public_key) + auth_line = "command=\"#{ROOT_PATH}/bin/gitlab-shell #{key_id}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{public_key}" end def rm_key - $logger.info "Removing key #{@key_id}" - Tempfile.open('authorized_keys') do |temp| - cmd = "sed '/shell #{@key_id}\"/d' #{auth_file} > #{temp.path} && mv #{temp.path} #{auth_file}" - system(cmd) + lock do + $logger.info "Removing key #{@key_id}" + Tempfile.open('authorized_keys') do |temp| + open(auth_file, 'r+') do |current| + current.each do |line| + temp.puts(line) unless line.include?("/bin/gitlab-shell #{@key_id}\"") + end + end + temp.close + FileUtils.cp(temp.path, auth_file) + end end + true end def clear - system("echo '# Managed by gitlab-shell' > #{auth_file}") + open(auth_file, 'w') { |file| file.puts '# Managed by gitlab-shell' } + true + end + + + def lock(timeout = 10) + File.open(lock_file, "w+") do |f| + begin + f.flock File::LOCK_EX + Timeout::timeout(timeout) { yield } + ensure + f.flock File::LOCK_UN + end + end + end + + def lock_file + @lock_file ||= auth_file + '.lock' end end diff --git a/lib/gitlab_net.rb b/lib/gitlab_net.rb index 99d0044..e6478ef 100644 --- a/lib/gitlab_net.rb +++ b/lib/gitlab_net.rb @@ -6,15 +6,25 @@ require_relative 'gitlab_config' require_relative 'gitlab_logger' class GitlabNet - def allowed?(cmd, repo, key, ref) + def allowed?(cmd, repo, actor, changes) project_name = repo.gsub("'", "") project_name = project_name.gsub(/\.git\Z/, "") project_name = project_name.gsub(/\A\//, "") - key_id = key.gsub("key-", "") + params = { + action: cmd, + changes: changes, + project: project_name, + } + + if actor =~ /\Akey\-\d+\Z/ + params.merge!(key_id: actor.gsub("key-", "")) + elsif actor =~ /\Auser\-\d+\Z/ + params.merge!(user_id: actor.gsub("user-", "")) + end - url = "#{host}/allowed?key_id=#{key_id}&action=#{cmd}&ref=#{ref}&project=#{project_name}" - resp = get(url) + url = "#{host}/allowed" + resp = post(url, params) !!(resp.code == '200' && resp.body == 'true') end @@ -39,37 +49,63 @@ class GitlabNet "#{config.gitlab_url}/api/v3/internal" end + def http_client_for(url) + Net::HTTP.new(url.host, url.port).tap do |http| + if URI::HTTPS === url + http.use_ssl = true + http.cert_store = cert_store + http.verify_mode = OpenSSL::SSL::VERIFY_NONE if config.http_settings['self_signed_cert'] + end + end + end + + def http_request_for(url, method = :get) + user = config.http_settings['user'] + password = config.http_settings['password'] + + if method == :get + Net::HTTP::Get.new(url.request_uri).tap { |r| r.basic_auth(user, password) if user && password } + else + Net::HTTP::Post.new(url.request_uri).tap { |r| r.basic_auth(user, password) if user && password } + end + end + def get(url) $logger.debug "Performing GET #{url}" url = URI.parse(url) - http = Net::HTTP.new(url.host, url.port) - - if URI::HTTPS === url - http.use_ssl = true - http.cert_store = cert_store + http = http_client_for url + request = http_request_for url + request.set_form_data(secret_token: secret_token) - if config.http_settings['self_signed_cert'] - http.verify_mode = OpenSSL::SSL::VERIFY_NONE + http.start { |http| http.request(request) }.tap do |resp| + if resp.code == "200" + $logger.debug { "Received response #{resp.code} => <#{resp.body}>." } + else + $logger.error { "API call <GET #{url}> failed: #{resp.code} => <#{resp.body}>." } end end + end - request = Net::HTTP::Get.new(url.request_uri) - if config.http_settings['user'] && config.http_settings['password'] - request.basic_auth config.http_settings['user'], config.http_settings['password'] - end + def post(url, params) + $logger.debug "Performing POST #{url}" - http.start {|http| http.request(request) }.tap do |resp| + url = URI.parse(url) + http = http_client_for(url) + request = http_request_for(url, :post) + request.set_form_data(params.merge(secret_token: secret_token)) + + http.start { |http| http.request(request) }.tap do |resp| if resp.code == "200" $logger.debug { "Received response #{resp.code} => <#{resp.body}>." } else - $logger.error { "API call <GET #{url}> failed: #{resp.code} => <#{resp.body}>." } + $logger.error { "API call <POST #{url}> failed: #{resp.code} => <#{resp.body}>." } end end end def cert_store - @cert_store ||= OpenSSL::X509::Store.new.tap { |store| + @cert_store ||= OpenSSL::X509::Store.new.tap do |store| store.set_default_paths if ca_file = config.http_settings['ca_file'] @@ -79,6 +115,10 @@ class GitlabNet if ca_path = config.http_settings['ca_path'] store.add_path(ca_path) end - } + end + end + + def secret_token + @secret_token ||= File.read File.join(ROOT_PATH, '.gitlab_shell_secret') end end diff --git a/lib/gitlab_post_receive.rb b/lib/gitlab_post_receive.rb new file mode 100644 index 0000000..bd80408 --- /dev/null +++ b/lib/gitlab_post_receive.rb @@ -0,0 +1,31 @@ +require_relative 'gitlab_init' +require 'json' + +class GitlabPostReceive + attr_reader :config, :repo_path, :changes + + def initialize(repo_path, actor, changes) + @config = GitlabConfig.new + @repo_path, @actor = repo_path.strip, actor + @changes = changes + end + + def exec + # reset GL_ID env since we already + # get value from it + ENV['GL_ID'] = nil + + update_redis + end + + protected + + def update_redis + queue = "#{config.redis_namespace}:queue:post_receive" + msg = JSON.dump({'class' => 'PostReceive', 'args' => [@repo_path, @actor, @changes]}) + unless system(*config.redis_command, 'rpush', queue, msg, err: '/dev/null', out: '/dev/null') + puts "GitLab: An unexpected error occurred (redis-cli returned #{$?.exitstatus})." + exit 1 + end + end +end diff --git a/lib/gitlab_projects.rb b/lib/gitlab_projects.rb index bea5686..82ae519 100644 --- a/lib/gitlab_projects.rb +++ b/lib/gitlab_projects.rb @@ -1,10 +1,12 @@ -require 'open3' require 'fileutils' +require 'timeout' require_relative 'gitlab_config' require_relative 'gitlab_logger' class GitlabProjects + GLOBAL_HOOKS_DIRECTORY = File.join(ROOT_PATH, 'hooks') + # Project name is a directory name for repository with .git at the end # It may be namespaced or not. Like repo.git or gitlab/repo.git attr_reader :project_name @@ -17,11 +19,19 @@ class GitlabProjects # Ex /home/git/repositories/test.git attr_reader :full_path + def self.create_hooks(path) + local_hooks_directory = File.join(path, 'hooks') + unless File.realpath(local_hooks_directory) == File.realpath(GLOBAL_HOOKS_DIRECTORY) + FileUtils.mv(local_hooks_directory, "#{local_hooks_directory}.old.#{Time.now.to_i}") + FileUtils.ln_s(GLOBAL_HOOKS_DIRECTORY, local_hooks_directory) + end + end + def initialize @command = ARGV.shift @project_name = ARGV.shift @repos_path = GitlabConfig.new.repos_path - @full_path = File.join(@repos_path, @project_name) + @full_path = File.join(@repos_path, @project_name) unless @project_name.nil? end def exec @@ -31,6 +41,7 @@ class GitlabProjects when 'create-tag'; create_tag when 'rm-tag'; rm_tag when 'add-project'; add_project + when 'list-projects'; puts list_projects when 'rm-project'; rm_project when 'mv-project'; mv_project when 'import-project'; import_project @@ -48,38 +59,46 @@ class GitlabProjects def create_branch branch_name = ARGV.shift ref = ARGV.shift || "HEAD" - cmd = "cd #{full_path} && git branch #{branch_name} #{ref}" - system(cmd) + cmd = %W(git --git-dir=#{full_path} branch -- #{branch_name} #{ref}) + system(*cmd) end def rm_branch branch_name = ARGV.shift - cmd = "cd #{full_path} && git branch -D #{branch_name}" - system(cmd) + cmd = %W(git --git-dir=#{full_path} branch -D #{branch_name}) + system(*cmd) end def create_tag tag_name = ARGV.shift ref = ARGV.shift || "HEAD" - cmd = "cd #{full_path} && git tag #{tag_name} #{ref}" - system(cmd) + cmd = %W(git --git-dir=#{full_path} tag) + if ARGV.size > 0 + msg = ARGV.shift + cmd += %W(-a -m #{msg}) + end + cmd += %W(-- #{tag_name} #{ref}) + system(*cmd) end def rm_tag tag_name = ARGV.shift - cmd = "cd #{full_path} && git tag -d #{tag_name}" - system(cmd) + cmd = %W(git --git-dir=#{full_path} tag -d #{tag_name}) + system(*cmd) end def add_project $logger.info "Adding project #{@project_name} at <#{full_path}>." FileUtils.mkdir_p(full_path, mode: 0770) - cmd = "cd #{full_path} && git init --bare && #{create_hooks_cmd}" - system(cmd) + cmd = %W(git --git-dir=#{full_path} init --bare) + system(*cmd) && self.class.create_hooks(full_path) end - def create_hooks_cmd - create_hooks_to(full_path) + def list_projects + $logger.info 'Listing projects' + Dir.chdir(repos_path) do + next Dir.glob('**/*.git') + end end def rm_project @@ -87,13 +106,53 @@ class GitlabProjects FileUtils.rm_rf(full_path) end + def mask_password_in_url(url) + result = URI(url) + result.password = "*****" unless result.password.nil? + result + rescue + url + end + + def remove_origin_in_repo + cmd = %W(git --git-dir=#{full_path} remote rm origin) + pid = Process.spawn(*cmd) + Process.wait(pid) + end + # Import project via git clone --bare # URL must be publicly cloneable def import_project + # Skip import if repo already exists + return false if File.exists?(full_path) + @source = ARGV.shift - $logger.info "Importing project #{@project_name} from <#{@source}> to <#{full_path}>." - cmd = "cd #{repos_path} && git clone --bare #{@source} #{project_name} && #{create_hooks_cmd}" - system(cmd) + masked_source = mask_password_in_url(@source) + + # timeout for clone + timeout = (ARGV.shift || 120).to_i + $logger.info "Importing project #{@project_name} from <#{masked_source}> to <#{full_path}>." + cmd = %W(git clone --bare -- #{@source} #{full_path}) + + pid = Process.spawn(*cmd) + + begin + Timeout.timeout(timeout) do + Process.wait(pid) + end + rescue Timeout::Error + $logger.error "Importing project #{@project_name} from <#{masked_source}> failed due to timeout." + + Process.kill('KILL', pid) + Process.wait + FileUtils.rm_rf(full_path) + false + else + self.class.create_hooks(full_path) + # The project was imported successfully. + # Remove the origin URL since it may contain password. + remove_origin_in_repo + end end # Move repository from one directory to another @@ -154,8 +213,8 @@ class GitlabProjects end $logger.info "Forking project from <#{full_path}> to <#{full_destination_path}>." - cmd = "cd #{namespaced_path} && git clone --bare #{full_path} && #{create_hooks_to(full_destination_path)}" - system(cmd) + cmd = %W(git clone --bare -- #{full_path} #{full_destination_path}) + system(*cmd) && self.class.create_hooks(full_destination_path) end def update_head @@ -166,11 +225,6 @@ class GitlabProjects return false end - unless File.exists?(File.join(full_path, 'refs/heads', new_head)) - $logger.error "update-head failed: specified branch does not exist in ref/heads." - return false - end - File.open(File.join(full_path, 'HEAD'), 'w') do |f| f.write("ref: refs/heads/#{new_head}") end @@ -178,10 +232,4 @@ class GitlabProjects $logger.info "Update head in project #{project_name} to <#{new_head}>." true end - - private - - def create_hooks_to(dest_path) - "ln -s #{File.join(ROOT_PATH, 'hooks', 'update')} #{dest_path}/hooks/update" - end end diff --git a/lib/gitlab_shell.rb b/lib/gitlab_shell.rb index 01ef4a1..e2cb2cc 100644 --- a/lib/gitlab_shell.rb +++ b/lib/gitlab_shell.rb @@ -1,8 +1,10 @@ -require 'open3' +require 'shellwords' require_relative 'gitlab_net' class GitlabShell + class DisallowedCommandError < StandardError; end + attr_accessor :key_id, :repo_name, :git_cmd, :repos_path, :repo_name def initialize @@ -10,7 +12,6 @@ class GitlabShell @origin_cmd = ENV['SSH_ORIGINAL_COMMAND'] @config = GitlabConfig.new @repos_path = @config.repos_path - @user_tried = false end def exec @@ -28,21 +29,24 @@ class GitlabShell $stderr.puts "Access denied." end else - message = "gitlab-shell: Attempt to execute disallowed command <#{@origin_cmd}> by #{log_username}." - $logger.warn message - puts 'Not allowed command' + raise DisallowedCommandError end else puts "Welcome to GitLab, #{username}!" end + rescue DisallowedCommandError => ex + message = "gitlab-shell: Attempt to execute disallowed command <#{@origin_cmd}> by #{log_username}." + $logger.warn message + puts 'Not allowed command' end protected def parse_cmd - args = @origin_cmd.split(' ') - @git_cmd = args.shift - @repo_name = args.shift + args = Shellwords.shellwords(@origin_cmd) + raise DisallowedCommandError unless args.count == 2 + @git_cmd = args[0] + @repo_name = escape_path(args[1]) end def git_cmds @@ -51,17 +55,17 @@ class GitlabShell def process_cmd repo_full_path = File.join(repos_path, repo_name) - cmd = "#{@git_cmd} #{repo_full_path}" - $logger.info "gitlab-shell: executing git command <#{cmd}> for #{log_username}." - exec_cmd(cmd) + $logger.info "gitlab-shell: executing git command <#{@git_cmd} #{repo_full_path}> for #{log_username}." + exec_cmd(@git_cmd, repo_full_path) end def validate_access api.allowed?(@git_cmd, @repo_name, @key_id, '_any') end - def exec_cmd args - Kernel::exec args + # This method is not covered by Rspec because it ends the current Ruby process. + def exec_cmd(*args) + Kernel::exec({'PATH' => ENV['PATH'], 'LD_LIBRARY_PATH' => ENV['LD_LIBRARY_PATH'], 'GL_ID' => ENV['GL_ID']}, *args, unsetenv_others: true) end def api @@ -70,10 +74,9 @@ class GitlabShell def user # Can't use "@user ||=" because that will keep hitting the API when @user is really nil! - if @user_tried + if instance_variable_defined?('@user') @user else - @user_tried = true @user = api.discover(@key_id) end end @@ -86,4 +89,14 @@ class GitlabShell def log_username @config.audit_usernames ? username : "user with key #{@key_id}" end + + def escape_path(path) + full_repo_path = File.join(repos_path, path) + + if File.absolute_path(full_repo_path) == full_repo_path + path + else + abort "Wrong repository path" + end + end end diff --git a/lib/gitlab_update.rb b/lib/gitlab_update.rb deleted file mode 100644 index 40ddc6e..0000000 --- a/lib/gitlab_update.rb +++ /dev/null @@ -1,59 +0,0 @@ -require_relative 'gitlab_init' -require_relative 'gitlab_net' - -class GitlabUpdate - attr_reader :config - - def initialize(repo_path, key_id, refname) - @config = GitlabConfig.new - - @repo_path = repo_path.strip - @repo_name = repo_path - @repo_name.gsub!(config.repos_path.to_s, "") - @repo_name.gsub!(/\.git$/, "") - @repo_name.gsub!(/^\//, "") - - @key_id = key_id - @refname = refname - @branch_name = /refs\/heads\/([\/\w\.-]+)/.match(refname).to_a.last - - @oldrev = ARGV[1] - @newrev = ARGV[2] - end - - def exec - # reset GL_ID env since we already - # get value from it - ENV['GL_ID'] = nil - - # If its push over ssh - # we need to check user permission per branch first - if ssh? - if api.allowed?('git-receive-pack', @repo_name, @key_id, @branch_name) - update_redis - exit 0 - else - puts "GitLab: You are not allowed to access #{@branch_name}! " - exit 1 - end - else - update_redis - exit 0 - end - end - - protected - - def api - GitlabNet.new - end - - def ssh? - @key_id =~ /\Akey\-\d+\Z/ - end - - def update_redis - command = "#{config.redis_command} rpush '#{config.redis_namespace}:queue:post_receive' '{\"class\":\"PostReceive\",\"args\":[\"#{@repo_path}\",\"#{@oldrev}\",\"#{@newrev}\",\"#{@refname}\",\"#{@key_id}\"]}' > /dev/null 2>&1" - system(command) - end -end diff --git a/lib/names_helper.rb b/lib/names_helper.rb new file mode 100644 index 0000000..efad56f --- /dev/null +++ b/lib/names_helper.rb @@ -0,0 +1,13 @@ +module NamesHelper + def extract_repo_name(path, base) + repo_name = path.strip + repo_name.gsub!(base, "") + repo_name.gsub!(/\.git$/, "") + repo_name.gsub!(/^\//, "") + repo_name + end + + def extract_ref_name(ref) + ref.gsub(/\Arefs\/(tags|heads)\//, '') + end +end diff --git a/spec/gitlab_access_spec.rb b/spec/gitlab_access_spec.rb new file mode 100644 index 0000000..13db4bd --- /dev/null +++ b/spec/gitlab_access_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' +require 'gitlab_access' + +describe GitlabAccess do + let(:repository_path) { "/home/git/repositories" } + let(:repo_name) { 'dzaporozhets/gitlab-ci' } + let(:repo_path) { File.join(repository_path, repo_name) + ".git" } + let(:gitlab_access) { GitlabAccess.new(repo_path, 'key-123', 'wow') } + + before do + GitlabConfig.any_instance.stub(repos_path: repository_path) + end + + describe :initialize do + it { gitlab_access.repo_name.should == repo_name } + it { gitlab_access.repo_path.should == repo_path } + it { gitlab_access.changes.should == ['wow'] } + end +end diff --git a/spec/gitlab_config_spec.rb b/spec/gitlab_config_spec.rb new file mode 100644 index 0000000..52fb182 --- /dev/null +++ b/spec/gitlab_config_spec.rb @@ -0,0 +1,81 @@ +require_relative 'spec_helper' +require_relative '../lib/gitlab_config' + +describe GitlabConfig do + let(:config) { GitlabConfig.new } + + describe :redis do + before do + config.instance_variable_set(:@config, YAML.load(<<eos +redis: + bin: /usr/bin/redis-cli + host: 127.0.1.1 + port: 6378 + pass: secure + database: 1 + socket: /var/run/redis/redis.sock + namespace: my:gitlab +eos + )) + end + + it { config.redis['bin'].should eq('/usr/bin/redis-cli') } + it { config.redis['host'].should eq('127.0.1.1') } + it { config.redis['port'].should eq(6378) } + it { config.redis['database'].should eq(1) } + it { config.redis['namespace'].should eq('my:gitlab') } + it { config.redis['socket'].should eq('/var/run/redis/redis.sock') } + it { config.redis['pass'].should eq('secure') } + end + + describe :gitlab_url do + let(:url) { 'http://test.com' } + subject { config.gitlab_url } + before { config.send(:config)['gitlab_url'] = url } + + it { should_not be_empty } + it { should eq(url) } + end + + describe :audit_usernames do + subject { config.audit_usernames } + + it("returns false by default") { should eq(false) } + end + + describe :redis_command do + subject { config.redis_command } + + context "with empty redis config" do + before do + config.stub(:redis) { {} } + end + + it { should be_an(Array) } + it { should include('redis-cli') } + end + + context "with host and port" do + before do + config.stub(:redis) { {'host' => 'localhost', 'port' => 1123, 'bin' => '/usr/bin/redis-cli'} } + end + + it { should be_an(Array) } + it { should include(config.redis['host']) } + it { should include(config.redis['bin']) } + it { should include(config.redis['port'].to_s) } + end + + context "with redis socket" do + let(:socket) { '/tmp/redis.socket' } + before do + config.stub(:redis) { {'bin' => '', 'socket' => socket } } + end + + it { should be_an(Array) } + it { should include(socket) } + it { should_not include('-p') } + it { should_not include('-h') } + end + end +end diff --git a/spec/gitlab_keys_spec.rb b/spec/gitlab_keys_spec.rb index c888b8e..bcce628 100644 --- a/spec/gitlab_keys_spec.rb +++ b/spec/gitlab_keys_spec.rb @@ -1,5 +1,6 @@ require_relative 'spec_helper' require_relative '../lib/gitlab_keys' +require 'stringio' describe GitlabKeys do before do @@ -14,36 +15,131 @@ describe GitlabKeys do it { gitlab_keys.instance_variable_get(:@key_id).should == 'key-741' } end + describe :add_key do let(:gitlab_keys) { build_gitlab_keys('add-key', 'key-741', 'ssh-rsa AAAAB3NzaDAxx2E') } - it "should receive valid cmd" do - valid_cmd = "echo 'command=\"#{ROOT_PATH}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E' >> #{GitlabConfig.new.auth_file}" - gitlab_keys.should_receive(:system).with(valid_cmd) + it "adds a line at the end of the file" do + create_authorized_keys_fixture 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" + File.read(tmp_authorized_keys_path).should == "existing content\n#{auth_line}\n" + end + + context "without file writing" do + before { gitlab_keys.stub(:open) } + before { create_authorized_keys_fixture } + + it "should log an add-key event" do + $logger.should_receive(:info).with('Adding key key-741 => "ssh-rsa AAAAB3NzaDAxx2E"') + gitlab_keys.send :add_key + end + + it "should return true" do + gitlab_keys.send(:add_key).should be_true + end + end + end + + describe :list_keys do + let(:gitlab_keys) do + build_gitlab_keys('add-key', 'key-741', 'ssh-rsa AAAAB3NzaDAxx2E') end - it "should log an add-key event" do - $logger.should_receive(:info).with('Adding key key-741 => "ssh-rsa AAAAB3NzaDAxx2E"') + it 'adds a key and lists it' do + create_authorized_keys_fixture gitlab_keys.send :add_key + auth_line1 = 'key-741 AAAAB3NzaDAxx2E' + gitlab_keys.send(:list_keys).should == "#{auth_line1}\n" end end + describe :batch_add_keys do + let(:gitlab_keys) { build_gitlab_keys('batch-add-keys') } + let(:fake_stdin) { StringIO.new("key-12\tssh-dsa ASDFASGADG\nkey-123\tssh-rsa GFDGDFSGSDFG\n", 'r') } + before do + create_authorized_keys_fixture + gitlab_keys.stub(stdin: fake_stdin) + end + + it "adds lines at the end of the file" do + 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_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" + end + + 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') } + + it "aborts" do + gitlab_keys.should_receive(:abort) + gitlab_keys.send :batch_add_keys + end + end + + context "without file writing" do + before do + gitlab_keys.should_receive(:open).and_yield(mock(:file, puts: nil)) + end + + it "should log an add-key event" do + $logger.should_receive(:info).with('Adding key key-12 => "ssh-dsa ASDFASGADG"') + $logger.should_receive(:info).with('Adding key key-123 => "ssh-rsa GFDGDFSGSDFG"') + gitlab_keys.send :batch_add_keys + end + + it "should return true" do + gitlab_keys.send(:batch_add_keys).should be_true + end + end + end + + describe :stdin do + let(:gitlab_keys) { build_gitlab_keys } + subject { gitlab_keys.send :stdin } + before { $stdin = 1 } + + it { should equal(1) } + end + describe :rm_key do let(:gitlab_keys) { build_gitlab_keys('rm-key', 'key-741', 'ssh-rsa AAAAB3NzaDAxx2E') } - let(:temp_file) { mock(:temp_file, path: 'tmp_path') } - before { Tempfile.should_receive(:open).and_yield(temp_file) } - it "should receive valid cmd" do - auth_file = GitlabConfig.new.auth_file - valid_cmd = "sed '/shell key-741\"/d' #{auth_file} > tmp_path && mv tmp_path #{auth_file}" - gitlab_keys.should_receive(:system).with(valid_cmd) + it "removes the right line" do + create_authorized_keys_fixture + other_line = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E" + open(tmp_authorized_keys_path, 'a') do |auth_file| + auth_file.puts "command=\"#{ROOT_PATH}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E" + auth_file.puts other_line + end gitlab_keys.send :rm_key + File.read(tmp_authorized_keys_path).should == "existing content\n#{other_line}\n" end - it "should log an rm-key event" do - $logger.should_receive(:info).with('Removing key key-741') - gitlab_keys.send :rm_key + context "without file writing" do + before do + Tempfile.stub(:open) + gitlab_keys.stub(:lock).and_yield + end + + it "should log an rm-key event" do + $logger.should_receive(:info).with('Removing key key-741') + gitlab_keys.send :rm_key + end + + it "should return true" do + gitlab_keys.send(:rm_key).should be_true + end + end + end + + describe :clear do + let(:gitlab_keys) { build_gitlab_keys('clear') } + + it "should return true" do + gitlab_keys.stub(:open) + gitlab_keys.send(:clear).should be_true end end @@ -54,12 +150,24 @@ describe GitlabKeys do gitlab_keys.exec end + it 'batch-add-keys arg should execute batch_add_keys method' do + gitlab_keys = build_gitlab_keys('batch-add-keys') + gitlab_keys.should_receive(:batch_add_keys) + gitlab_keys.exec + end + it 'rm-key arg should execute rm_key method' do gitlab_keys = build_gitlab_keys('rm-key') gitlab_keys.should_receive(:rm_key) gitlab_keys.exec end + it 'clear arg should execute clear method' do + gitlab_keys = build_gitlab_keys('clear') + gitlab_keys.should_receive(:clear) + gitlab_keys.exec + end + it 'should puts message if unknown command arg' do gitlab_keys = build_gitlab_keys('change-key') gitlab_keys.should_receive(:puts).with('not allowed') @@ -74,6 +182,46 @@ describe GitlabKeys do end end + describe :lock do + before do + GitlabKeys.any_instance.stub(lock_file: tmp_lock_file_path) + end + + it "should raise exception if operation lasts more then timeout" do + key = GitlabKeys.new + expect do + key.send :lock, 1 do + sleep 2 + end + end.to raise_error + end + + it "should actually lock file" do + $global = "" + key = GitlabKeys.new + + thr1 = Thread.new do + key.send :lock do + # Put bigger sleep here to test if main thread will + # wait for lock file released before executing code + sleep 1 + $global << "foo" + end + end + + # make sure main thread start lock command after + # thread above + sleep 0.5 + + key.send :lock do + $global << "bar" + end + + thr1.join + $global.should == "foobar" + end + end + def build_gitlab_keys(*args) argv(*args) GitlabKeys.new @@ -84,4 +232,18 @@ describe GitlabKeys do ARGV[i] = arg end end + + def create_authorized_keys_fixture + FileUtils.mkdir_p(File.dirname(tmp_authorized_keys_path)) + open(tmp_authorized_keys_path, 'w') { |file| file.puts('existing content') } + gitlab_keys.stub(auth_file: tmp_authorized_keys_path) + end + + def tmp_authorized_keys_path + File.join(ROOT_PATH, 'tmp', 'authorized_keys') + end + + def tmp_lock_file_path + tmp_authorized_keys_path + '.lock' + end end diff --git a/spec/gitlab_logger_spec.rb b/spec/gitlab_logger_spec.rb new file mode 100644 index 0000000..7d8df76 --- /dev/null +++ b/spec/gitlab_logger_spec.rb @@ -0,0 +1,11 @@ +require_relative 'spec_helper' +require_relative '../lib/gitlab_logger' + +describe :convert_log_level do + subject { convert_log_level :extreme } + + it "converts invalid log level to Logger::INFO" do + $stderr.should_receive(:puts).at_least(:once) + should eq(Logger::INFO) + end +end diff --git a/spec/gitlab_net_spec.rb b/spec/gitlab_net_spec.rb index 965fa8e..9ccabe0 100644 --- a/spec/gitlab_net_spec.rb +++ b/spec/gitlab_net_spec.rb @@ -4,9 +4,11 @@ require_relative '../lib/gitlab_net' describe GitlabNet, vcr: true do let(:gitlab_net) { GitlabNet.new } + let(:changes) { ['0000000000000000000000000000000000000000 92d0970eefd7acb6d548878925ce2208cfe2d2ec refs/heads/branch4'] } before do gitlab_net.stub!(:host).and_return('https://dev.gitlab.org/api/v3/internal') + gitlab_net.stub!(:secret_token).and_return('a123') end describe :check do @@ -16,29 +18,50 @@ describe GitlabNet, vcr: true do result.code.should == '200' end end + + it 'adds the secret_token to request' do + VCR.use_cassette("check-ok") do + Net::HTTP::Get.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: 'a123')) + gitlab_net.check + end + end end describe :discover do it 'should return user has based on key id' do VCR.use_cassette("discover-ok") do - user = gitlab_net.discover('key-1') + user = gitlab_net.discover('key-126') user['name'].should == 'Dmitriy Zaporozhets' end end + + it 'adds the secret_token to request' do + VCR.use_cassette("discover-ok") do + Net::HTTP::Get.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: 'a123')) + gitlab_net.discover('key-126') + end + end end describe :allowed? do context 'ssh key with access to project' do it 'should allow pull access for dev.gitlab.org' do VCR.use_cassette("allowed-pull") do - access = gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-1', 'master') + access = gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-126', changes) access.should be_true end end + it 'adds the secret_token theo request' do + VCR.use_cassette("allowed-pull") do + Net::HTTP::Post.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: 'a123')) + gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-126', changes) + end + end + it 'should allow push access for dev.gitlab.org' do VCR.use_cassette("allowed-push") do - access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'key-1', 'master') + access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'key-126', changes) access.should be_true end end @@ -47,17 +70,92 @@ describe GitlabNet, vcr: true do context 'ssh key without access to project' do it 'should deny pull access for dev.gitlab.org' do VCR.use_cassette("denied-pull") do - access = gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-2', 'master') + access = gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-2', changes) access.should be_false end end it 'should deny push access for dev.gitlab.org' do VCR.use_cassette("denied-push") do - access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'key-2', 'master') + access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'key-2', changes) + access.should be_false + end + end + + it 'should deny push access for dev.gitlab.org (with user)' do + VCR.use_cassette("denied-push-with-user") do + access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'user-1', changes) access.should be_false end end end end + + describe :host do + let(:net) { GitlabNet.new } + subject { net.send :host } + + it { should include(net.send(:config).gitlab_url) } + it("uses API version 3") { should include("api/v3") } + end + + describe :http_client_for do + subject { gitlab_net.send :http_client_for, URI('https://localhost/') } + before do + gitlab_net.stub! :cert_store + gitlab_net.send(:config).http_settings.stub(:[]).with('self_signed_cert') { true } + end + + its(:verify_mode) { should eq(OpenSSL::SSL::VERIFY_NONE) } + end + + describe :http_request_for do + let(:get) do + double(Net::HTTP::Get).tap do |get| + Net::HTTP::Get.stub(:new) { get } + end + end + let(:user) { 'user' } + let(:password) { 'password' } + let(:url) { URI 'http://localhost/' } + subject { gitlab_net.send :http_request_for, url } + + before do + gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user } + gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password } + get.should_receive(:basic_auth).with(user, password).once + end + + it { should_not be_nil } + end + + describe :cert_store do + let(:store) do + double(OpenSSL::X509::Store).tap do |store| + OpenSSL::X509::Store.stub(:new) { store } + end + end + + before :each do + store.should_receive(:set_default_paths).once + end + + after do + gitlab_net.send :cert_store + end + + it "calls add_file with http_settings['ca_file']" do + gitlab_net.send(:config).http_settings.stub(:[]).with('ca_file') { 'test_file' } + gitlab_net.send(:config).http_settings.stub(:[]).with('ca_path') { nil } + store.should_receive(:add_file).with('test_file') + store.should_not_receive(:add_path) + end + + it "calls add_path with http_settings['ca_path']" do + gitlab_net.send(:config).http_settings.stub(:[]).with('ca_file') { nil } + gitlab_net.send(:config).http_settings.stub(:[]).with('ca_path') { 'test_path' } + store.should_not_receive(:add_file) + store.should_receive(:add_path).with('test_path') + end + end end diff --git a/spec/gitlab_post_receive_spec.rb b/spec/gitlab_post_receive_spec.rb new file mode 100644 index 0000000..50c6f0a --- /dev/null +++ b/spec/gitlab_post_receive_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' +require 'gitlab_post_receive' + +describe GitlabPostReceive do + let(:repository_path) { "/home/git/repositories" } + let(:repo_name) { 'dzaporozhets/gitlab-ci' } + let(:repo_path) { File.join(repository_path, repo_name) + ".git" } + let(:gitlab_post_receive) { GitlabPostReceive.new(repo_path, 'key-123', 'wow') } + + before do + GitlabConfig.any_instance.stub(repos_path: repository_path) + end + + describe :initialize do + it { gitlab_post_receive.repo_path.should == repo_path } + it { gitlab_post_receive.changes.should == 'wow' } + end +end diff --git a/spec/gitlab_projects_spec.rb b/spec/gitlab_projects_spec.rb index 6ff72ec..a592e62 100644 --- a/spec/gitlab_projects_spec.rb +++ b/spec/gitlab_projects_spec.rb @@ -31,8 +31,8 @@ describe GitlabProjects do it "should create a branch" do gl_projects_create.exec gl_projects.exec - branch_ref = `cd #{tmp_repo_path} && git rev-parse test_branch`.strip - master_ref = `cd #{tmp_repo_path} && git rev-parse master`.strip + branch_ref = capture_in_tmp_repo(%W(git rev-parse test_branch)) + master_ref = capture_in_tmp_repo(%W(git rev-parse master)) branch_ref.should == master_ref end end @@ -49,9 +49,9 @@ describe GitlabProjects do it "should remove a branch" do gl_projects_create.exec gl_projects_create_branch.exec - branch_ref = `cd #{tmp_repo_path} && git rev-parse test_branch`.strip + branch_ref = capture_in_tmp_repo(%W(git rev-parse test_branch)) gl_projects.exec - branch_del = `cd #{tmp_repo_path} && git rev-parse test_branch`.strip + branch_del = capture_in_tmp_repo(%W(git rev-parse test_branch)) branch_del.should_not == branch_ref end end @@ -60,14 +60,36 @@ describe GitlabProjects do let(:gl_projects_create) { build_gitlab_projects('import-project', repo_name, 'https://github.com/randx/six.git') } - let(:gl_projects) { build_gitlab_projects('create-tag', repo_name, 'test_tag', 'master') } + context "lightweight tag" do + let(:gl_projects) { build_gitlab_projects('create-tag', repo_name, 'test_tag', 'master') } + + it "should create a tag" do + gl_projects_create.exec + gl_projects.exec + tag_ref = capture_in_tmp_repo(%W(git rev-parse test_tag)) + master_ref = capture_in_tmp_repo(%W(git rev-parse master)) + tag_ref.should == master_ref + end + end + context "annotated tag" do + msg = 'some message' + tag_name = 'test_annotated_tag' - it "should create a tag" do - gl_projects_create.exec - gl_projects.exec - tag_ref = `cd #{tmp_repo_path} && git rev-parse test_tag`.strip - master_ref = `cd #{tmp_repo_path} && git rev-parse master`.strip - tag_ref.should == master_ref + let(:gl_projects) { build_gitlab_projects('create-tag', repo_name, tag_name, 'master', msg) } + + it "should create an annotated tag" do + gl_projects_create.exec + system(*%W(git --git-dir=#{tmp_repo_path} config user.name Joe)) + system(*%W(git --git-dir=#{tmp_repo_path} config user.email joe@smith.com)) + gl_projects.exec + + tag_ref = capture_in_tmp_repo(%W(git rev-parse #{tag_name}^{})) + master_ref = capture_in_tmp_repo(%W(git rev-parse master)) + tag_msg = capture_in_tmp_repo(%W(git tag -l -n1 #{tag_name})) + + tag_ref.should == master_ref + tag_msg.should == tag_name + ' ' + msg + end end end @@ -83,9 +105,9 @@ describe GitlabProjects do it "should remove a branch" do gl_projects_create.exec gl_projects_create_tag.exec - branch_ref = `cd #{tmp_repo_path} && git rev-parse test_tag`.strip + branch_ref = capture_in_tmp_repo(%W(git rev-parse test_tag)) gl_projects.exec - branch_del = `cd #{tmp_repo_path} && git rev-parse test_tag`.strip + branch_del = capture_in_tmp_repo(%W(git rev-parse test_tag)) branch_del.should_not == branch_ref end end @@ -95,14 +117,15 @@ describe GitlabProjects do it "should create a directory" do gl_projects.stub(system: true) + GitlabProjects.stub(create_hooks: true) gl_projects.exec File.exists?(tmp_repo_path).should be_true end it "should receive valid cmd" do - valid_cmd = "cd #{tmp_repo_path} && git init --bare" - valid_cmd << " && ln -s #{ROOT_PATH}/hooks/update #{tmp_repo_path}/hooks/update" - gl_projects.should_receive(:system).with(valid_cmd) + valid_cmd = ['git', "--git-dir=#{tmp_repo_path}", 'init', '--bare'] + gl_projects.should_receive(:system).with(*valid_cmd).and_return(true) + GitlabProjects.should_receive(:create_hooks).with(tmp_repo_path) gl_projects.exec end @@ -112,6 +135,22 @@ describe GitlabProjects do end end + describe :list_projects do + let(:gl_projects) do + build_gitlab_projects('add-project', "list_test/#{repo_name}") + end + + before do + FileUtils.mkdir_p(tmp_repos_path) + end + + it 'should create projects and list them' do + GitlabProjects.stub(create_hooks: true) + gl_projects.exec + gl_projects.send(:list_projects).should == ["list_test/#{repo_name}"] + end + end + describe :mv_project do let(:gl_projects) { build_gitlab_projects('mv-project', repo_name, 'repo.git') } let(:new_repo_path) { File.join(tmp_repos_path, 'repo.git') } @@ -175,11 +214,12 @@ describe GitlabProjects do describe :update_head do let(:gl_projects) { build_gitlab_projects('update-head', repo_name, 'stable') } + let(:gl_projects_fail) { build_gitlab_projects 'update-head', repo_name } before do FileUtils.mkdir_p(tmp_repo_path) - system("git init --bare #{tmp_repo_path}") - system("touch #{tmp_repo_path}/refs/heads/stable") + system(*%W(git init --bare #{tmp_repo_path})) + FileUtils.touch(File.join(tmp_repo_path, "refs/heads/stable")) File.read(File.join(tmp_repo_path, 'HEAD')).strip.should == 'ref: refs/heads/master' end @@ -192,20 +232,55 @@ describe GitlabProjects do $logger.should_receive(:info).with("Update head in project #{repo_name} to <stable>.") gl_projects.exec end + + it "should failed and log an error" do + $logger.should_receive(:error).with("update-head failed: no branch provided.") + gl_projects_fail.exec.should be_false + end end describe :import_project do - let(:gl_projects) { build_gitlab_projects('import-project', repo_name, 'https://github.com/randx/six.git') } + context 'success import' do + let(:gl_projects) { build_gitlab_projects('import-project', repo_name, 'https://github.com/randx/six.git') } - it "should import a repo" do - gl_projects.exec - File.exists?(File.join(tmp_repo_path, 'HEAD')).should be_true + it { gl_projects.exec.should be_true } + + it "should import a repo" do + gl_projects.exec + File.exists?(File.join(tmp_repo_path, 'HEAD')).should be_true + end + + it "should log an import-project event" do + message = "Importing project #{repo_name} from <https://github.com/randx/six.git> to <#{tmp_repo_path}>." + $logger.should_receive(:info).with(message) + gl_projects.exec + end end - it "should log an import-project event" do - message = "Importing project #{repo_name} from <https://github.com/randx/six.git> to <#{tmp_repo_path}>." - $logger.should_receive(:info).with(message) - gl_projects.exec + context 'already exists' do + let(:gl_projects) { build_gitlab_projects('import-project', repo_name, 'https://github.com/randx/six.git') } + + it 'should import only once' do + gl_projects.exec.should be_true + gl_projects.exec.should be_false + end + end + + context 'timeout' do + let(:gl_projects) { build_gitlab_projects('import-project', repo_name, 'https://github.com/gitlabhq/gitlabhq.git', '1') } + + it { gl_projects.exec.should be_false } + + it "should not import a repo" do + gl_projects.exec + File.exists?(File.join(tmp_repo_path, 'HEAD')).should be_false + end + + it "should log an import-project event" do + message = "Importing project #{repo_name} from <https://github.com/gitlabhq/gitlabhq.git> failed due to timeout." + $logger.should_receive(:error).with(message) + gl_projects.exec + end end end @@ -236,7 +311,8 @@ describe GitlabProjects do FileUtils.mkdir_p(File.join(tmp_repos_path, 'forked-to-namespace')) gl_projects_fork.exec.should be_true File.exists?(dest_repo).should be_true - File.exists?(File.join(dest_repo, '/hooks/update')).should be_true + File.exists?(File.join(dest_repo, '/hooks/pre-receive')).should be_true + File.exists?(File.join(dest_repo, '/hooks/post-receive')).should be_true end it "should not fork if a project of the same name already exists" do @@ -299,4 +375,8 @@ describe GitlabProjects do def repo_name 'gitlab-ci.git' end + + def capture_in_tmp_repo(cmd) + IO.popen([*cmd, {chdir: tmp_repo_path}]).read.strip + end end diff --git a/spec/gitlab_shell_spec.rb b/spec/gitlab_shell_spec.rb index 44dca6d..4741303 100644 --- a/spec/gitlab_shell_spec.rb +++ b/spec/gitlab_shell_spec.rb @@ -48,6 +48,14 @@ describe GitlabShell do its(:repo_name) { should == 'dmitriy.zaporozhets/gitlab-ci.git' } its(:git_cmd) { should == 'git-upload-pack' } end + + context 'with an invalid number of arguments' do + before { ssh_cmd 'foobar' } + + it "should raise an DisallowedCommandError" do + expect { subject.send :parse_cmd }.to raise_error(GitlabShell::DisallowedCommandError) + end + end end describe :exec do @@ -60,7 +68,7 @@ describe GitlabShell do end it "should execute the command" do - subject.should_receive(:exec_cmd).with("git-upload-pack #{File.join(repository_path, 'gitlab-ci.git')}") + subject.should_receive(:exec_cmd).with("git-upload-pack", File.join(repository_path, 'gitlab-ci.git')) end it "should set the GL_ID environment variable" do @@ -89,7 +97,7 @@ describe GitlabShell do end it "should execute the command" do - subject.should_receive(:exec_cmd).with("git-receive-pack #{File.join(repository_path, 'gitlab-ci.git')}") + subject.should_receive(:exec_cmd).with("git-receive-pack", File.join(repository_path, 'gitlab-ci.git')) end it "should log the command execution" do @@ -145,6 +153,31 @@ describe GitlabShell do end end + describe :exec_cmd do + let(:shell) { GitlabShell.new } + before { Kernel.stub!(:exec) } + + it "uses Kernel::exec method" do + Kernel.should_receive(:exec).with(kind_of(Hash), 1, unsetenv_others: true).once + shell.send :exec_cmd, 1 + end + end + + describe :api do + let(:shell) { GitlabShell.new } + subject { shell.send :api } + + it { should be_a(GitlabNet) } + end + + describe :escape_path do + let(:shell) { GitlabShell.new } + before { File.stub(:absolute_path) { 'y' } } + subject { -> { shell.send(:escape_path, 'z') } } + + it { should raise_error(SystemExit, "Wrong repository path") } + end + def ssh_cmd(cmd) ENV['SSH_ORIGINAL_COMMAND'] = cmd end diff --git a/spec/names_helper_spec.rb b/spec/names_helper_spec.rb new file mode 100644 index 0000000..081dac9 --- /dev/null +++ b/spec/names_helper_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' +require 'names_helper' + +describe NamesHelper do + include NamesHelper + + describe :extract_repo_name do + it { extract_repo_name(' /opt/repos/randx/gitlab.git', '/opt/repos').should == 'randx/gitlab' } + it { extract_repo_name("/opt/repos/randx/gitlab.git\r\n", '/opt/repos/').should == 'randx/gitlab' } + end + + describe :extract_ref_name do + it { extract_ref_name('refs/heads/awesome-feature').should == 'awesome-feature' } + it { extract_ref_name('refs/tags/v2.2.1').should == 'v2.2.1' } + it { extract_ref_name('refs/tags/releases/v2.2.1').should == 'releases/v2.2.1' } + end +end + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ea3872d..612af76 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,11 @@ ROOT_PATH = File.expand_path(File.join(File.dirname(__FILE__), "..")) -if ENV['TRAVIS'] +if ENV['COVERALLS'] require 'coveralls' Coveralls.wear! +else + require 'simplecov' + SimpleCov.start end require 'vcr' diff --git a/spec/vcr_cassettes/allowed-pull.yml b/spec/vcr_cassettes/allowed-pull.yml index db2ef29..337b00f 100644 --- a/spec/vcr_cassettes/allowed-pull.yml +++ b/spec/vcr_cassettes/allowed-pull.yml @@ -1,16 +1,20 @@ --- http_interactions: - request: - method: get - uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-receive-pack&key_id=1&project=gitlab/gitlabhq&ref=master + method: post + uri: https://dev.gitlab.org/api/v3/internal/allowed body: encoding: US-ASCII - string: '' + string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&key_id=126&secret_token=a123 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 @@ -19,7 +23,7 @@ http_interactions: Server: - nginx/1.1.19 Date: - - Mon, 11 Mar 2013 12:13:31 GMT + - Wed, 03 Sep 2014 11:27:36 GMT Content-Type: - application/json Content-Length: @@ -28,21 +32,17 @@ http_interactions: - keep-alive Status: - 200 OK - X-Ua-Compatible: - - IE=Edge,chrome=1 Etag: - - ! '"b326b5062b2f0e69046810717534cb09"' + - '"b326b5062b2f0e69046810717534cb09"' Cache-Control: - max-age=0, private, must-revalidate X-Request-Id: - - ac00b27743e177559476f6575e45d1c2 + - 85bfa9b3-fddc-4810-a033-f1eabc04c66c X-Runtime: - - '0.017272' - X-Rack-Cache: - - miss + - '0.089741' body: - encoding: US-ASCII + encoding: UTF-8 string: 'true' http_version: - recorded_at: Mon, 11 Mar 2013 12:13:31 GMT + recorded_at: Wed, 03 Sep 2014 11:27:36 GMT recorded_with: VCR 2.4.0 diff --git a/spec/vcr_cassettes/allowed-push.yml b/spec/vcr_cassettes/allowed-push.yml index 96b8c9a..cb757bf 100644 --- a/spec/vcr_cassettes/allowed-push.yml +++ b/spec/vcr_cassettes/allowed-push.yml @@ -1,16 +1,20 @@ --- http_interactions: - request: - method: get - uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-upload-pack&key_id=1&project=gitlab/gitlabhq&ref=master + method: post + uri: https://dev.gitlab.org/api/v3/internal/allowed body: encoding: US-ASCII - string: '' + string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&key_id=126&secret_token=a123 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 @@ -19,7 +23,7 @@ http_interactions: Server: - nginx/1.1.19 Date: - - Mon, 11 Mar 2013 12:13:32 GMT + - Wed, 03 Sep 2014 11:27:37 GMT Content-Type: - application/json Content-Length: @@ -28,21 +32,17 @@ http_interactions: - keep-alive Status: - 200 OK - X-Ua-Compatible: - - IE=Edge,chrome=1 Etag: - - ! '"b326b5062b2f0e69046810717534cb09"' + - '"b326b5062b2f0e69046810717534cb09"' Cache-Control: - max-age=0, private, must-revalidate X-Request-Id: - - c65f8ea13ac34cf182fcb6daefeb1bdd + - 115e7a72-b3a6-49e1-b531-980c71088c9d X-Runtime: - - '0.018934' - X-Rack-Cache: - - miss + - '0.833195' body: - encoding: US-ASCII + encoding: UTF-8 string: 'true' http_version: - recorded_at: Mon, 11 Mar 2013 12:13:31 GMT + recorded_at: Wed, 03 Sep 2014 11:27:37 GMT recorded_with: VCR 2.4.0 diff --git a/spec/vcr_cassettes/check-ok.yml b/spec/vcr_cassettes/check-ok.yml index bac5d43..1ba8b04 100644 --- a/spec/vcr_cassettes/check-ok.yml +++ b/spec/vcr_cassettes/check-ok.yml @@ -5,10 +5,12 @@ http_interactions: uri: https://dev.gitlab.org/api/v3/internal/check body: encoding: US-ASCII - string: '' + string: secret_token=a123 headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - - ! '*/*' + - "*/*" User-Agent: - Ruby response: @@ -19,30 +21,26 @@ http_interactions: Server: - nginx/1.1.19 Date: - - Mon, 11 Mar 2013 13:05:30 GMT + - Wed, 03 Sep 2014 11:27:35 GMT Content-Type: - application/json Content-Length: - - '71' + - '72' Connection: - keep-alive Status: - 200 OK - X-Ua-Compatible: - - IE=Edge,chrome=1 Etag: - - ! '"cb194c2a4bfe01ae695d15ad834b7f5c"' + - '"23ee74bf6981b57b9d0e0d8a91f3ec1b"' Cache-Control: - max-age=0, private, must-revalidate X-Request-Id: - - 66424e020a58b1fc50efcf846e301b40 + - c389e282-4d7b-4041-9aed-336e5b007424 X-Runtime: - - '0.003728' - X-Rack-Cache: - - miss + - '0.958718' body: - encoding: US-ASCII - string: ! '{"api_version":"v3","gitlab_version":"5.0.0pre","gitlab_rev":"473efc8"}' + encoding: UTF-8 + string: '{"api_version":"v3","gitlab_version":"7.3.0.pre","gitlab_rev":"e8f1331"}' http_version: - recorded_at: Mon, 11 Mar 2013 13:05:30 GMT + recorded_at: Wed, 03 Sep 2014 11:27:35 GMT recorded_with: VCR 2.4.0 diff --git a/spec/vcr_cassettes/denied-pull.yml b/spec/vcr_cassettes/denied-pull.yml index ef71121..9941e70 100644 --- a/spec/vcr_cassettes/denied-pull.yml +++ b/spec/vcr_cassettes/denied-pull.yml @@ -1,16 +1,20 @@ --- http_interactions: - request: - method: get - uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-receive-pack&key_id=2&project=gitlab/gitlabhq&ref=master + method: post + uri: https://dev.gitlab.org/api/v3/internal/allowed body: encoding: US-ASCII - string: '' + string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&key_id=2&secret_token=a123 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 @@ -19,7 +23,7 @@ http_interactions: Server: - nginx/1.1.19 Date: - - Mon, 11 Mar 2013 12:21:31 GMT + - Wed, 03 Sep 2014 11:27:38 GMT Content-Type: - application/json Content-Length: @@ -28,19 +32,15 @@ http_interactions: - keep-alive Status: - 404 Not Found - X-Ua-Compatible: - - IE=Edge,chrome=1 Cache-Control: - no-cache X-Request-Id: - - bda6c4046ea050c5bad39a38337f5771 + - f9c23a82-be65-41c8-958a-5f3e1b9aa58b X-Runtime: - - '0.005632' - X-Rack-Cache: - - miss + - '0.028027' body: - encoding: US-ASCII - string: ! '{"message":"404 Not found"}' + encoding: UTF-8 + string: '{"message":"404 Not found"}' http_version: - recorded_at: Mon, 11 Mar 2013 12:21:30 GMT + recorded_at: Wed, 03 Sep 2014 11:27:38 GMT recorded_with: VCR 2.4.0 diff --git a/spec/vcr_cassettes/denied-push-with-user.yml b/spec/vcr_cassettes/denied-push-with-user.yml new file mode 100644 index 0000000..4694797 --- /dev/null +++ b/spec/vcr_cassettes/denied-push-with-user.yml @@ -0,0 +1,48 @@ +--- +http_interactions: +- request: + method: post + uri: https://dev.gitlab.org/api/v3/internal/allowed + body: + encoding: US-ASCII + string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&user_id=1&secret_token=a123 + 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: + Server: + - nginx/1.1.19 + Date: + - Wed, 03 Sep 2014 11:27:39 GMT + Content-Type: + - application/json + Content-Length: + - '4' + Connection: + - keep-alive + Status: + - 200 OK + Etag: + - '"b326b5062b2f0e69046810717534cb09"' + Cache-Control: + - max-age=0, private, must-revalidate + X-Request-Id: + - 8bd01f76-6029-4921-ba97-12323a7d5750 + X-Runtime: + - '0.019640' + body: + encoding: UTF-8 + string: 'false' + http_version: + recorded_at: Wed, 03 Sep 2014 11:27:39 GMT +recorded_with: VCR 2.4.0 diff --git a/spec/vcr_cassettes/denied-push.yml b/spec/vcr_cassettes/denied-push.yml index 8116aa1..fc0a309 100644 --- a/spec/vcr_cassettes/denied-push.yml +++ b/spec/vcr_cassettes/denied-push.yml @@ -1,16 +1,20 @@ --- http_interactions: - request: - method: get - uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-upload-pack&key_id=2&project=gitlab/gitlabhq&ref=master + method: post + uri: https://dev.gitlab.org/api/v3/internal/allowed body: encoding: US-ASCII - string: '' + string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&key_id=2&secret_token=a123 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 @@ -19,7 +23,7 @@ http_interactions: Server: - nginx/1.1.19 Date: - - Mon, 11 Mar 2013 12:21:31 GMT + - Wed, 03 Sep 2014 11:27:38 GMT Content-Type: - application/json Content-Length: @@ -28,19 +32,15 @@ http_interactions: - keep-alive Status: - 404 Not Found - X-Ua-Compatible: - - IE=Edge,chrome=1 Cache-Control: - no-cache X-Request-Id: - - 6242870566154e17170c86ebb7f7a448 + - 8b0210d2-4db5-4325-8120-740e22eaf7d5 X-Runtime: - - '0.005174' - X-Rack-Cache: - - miss + - '0.015198' body: - encoding: US-ASCII - string: ! '{"message":"404 Not found"}' + encoding: UTF-8 + string: '{"message":"404 Not found"}' http_version: - recorded_at: Mon, 11 Mar 2013 12:21:31 GMT + recorded_at: Wed, 03 Sep 2014 11:27:38 GMT recorded_with: VCR 2.4.0 diff --git a/spec/vcr_cassettes/dev_gitlab_org.yml b/spec/vcr_cassettes/dev_gitlab_org.yml deleted file mode 100644 index 02410e0..0000000 --- a/spec/vcr_cassettes/dev_gitlab_org.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-receive-pack&key_id=100&project=gitlab/gitlabhq&ref=master - body: - encoding: US-ASCII - string: '' - headers: - Accept: - - ! '*/*' - User-Agent: - - Ruby - response: - status: - code: 200 - message: OK - headers: - Server: - - nginx/1.1.19 - Date: - - Mon, 11 Mar 2013 12:13:05 GMT - Content-Type: - - application/json - Content-Length: - - '4' - Connection: - - keep-alive - Status: - - 200 OK - X-Ua-Compatible: - - IE=Edge,chrome=1 - Etag: - - ! '"b326b5062b2f0e69046810717534cb09"' - Cache-Control: - - max-age=0, private, must-revalidate - X-Request-Id: - - aabf04bba02ad88d256633dc34097095 - X-Runtime: - - '0.017099' - X-Rack-Cache: - - miss - body: - encoding: US-ASCII - string: 'true' - http_version: - recorded_at: Mon, 11 Mar 2013 12:13:05 GMT -recorded_with: VCR 2.4.0 diff --git a/spec/vcr_cassettes/discover-ok.yml b/spec/vcr_cassettes/discover-ok.yml index beec0c1..a86243c 100644 --- a/spec/vcr_cassettes/discover-ok.yml +++ b/spec/vcr_cassettes/discover-ok.yml @@ -2,13 +2,15 @@ http_interactions: - request: method: get - uri: https://dev.gitlab.org/api/v3/internal/discover?key_id=1 + uri: https://dev.gitlab.org/api/v3/internal/discover?key_id=126 body: encoding: US-ASCII - string: '' + string: secret_token=a123 headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - - ! '*/*' + - "*/*" User-Agent: - Ruby response: @@ -19,30 +21,26 @@ http_interactions: Server: - nginx/1.1.19 Date: - - Mon, 11 Mar 2013 12:25:45 GMT + - Wed, 03 Sep 2014 11:27:35 GMT Content-Type: - application/json Content-Length: - - '332' + - '56' Connection: - keep-alive Status: - 200 OK - X-Ua-Compatible: - - IE=Edge,chrome=1 Etag: - - ! '"132e22d01addb4fe7fc1ee9f954b69e1"' + - '"1d75c1cf3d4bfa4d2b7bb6a0bcfd7f55"' Cache-Control: - max-age=0, private, must-revalidate X-Request-Id: - - 47a6a45ce3073d1f7497d15290c109ef + - ef4513ae-0424-4941-8be0-b5a3a7b4bf12 X-Runtime: - - '0.007938' - X-Rack-Cache: - - miss + - '0.016934' body: - encoding: US-ASCII - string: ! '{"name":"Dmitriy Zaporozhets"}' + encoding: UTF-8 + string: '{"name":"Dmitriy Zaporozhets","username":"dzaporozhets"}' http_version: - recorded_at: Mon, 11 Mar 2013 12:25:44 GMT + recorded_at: Wed, 03 Sep 2014 11:27:35 GMT recorded_with: VCR 2.4.0 diff --git a/support/rewrite-hooks.sh b/support/rewrite-hooks.sh index 3c96b6f..585eaeb 100755 --- a/support/rewrite-hooks.sh +++ b/support/rewrite-hooks.sh @@ -1,28 +1,5 @@ #!/bin/bash +# This script is deprecated. Use bin/create-hooks instead. -# $1 is an optional argument specifying the location of the repositories directory. -# Defaults to /home/git/repositories if not provided - -home_dir="/home/git" -src=${1:-"$home_dir/repositories"} - -function create_link_in { - ln -s -f "$home_dir/gitlab-shell/hooks/update" "$1/hooks/update" -} - -for dir in `ls "$src/"` -do - if [ -d "$src/$dir" ]; then - if [[ "$dir" =~ ^.*\.git$ ]] - then - create_link_in "$src/$dir" - else - for subdir in `ls "$src/$dir/"` - do - if [ -d "$src/$dir/$subdir" ] && [[ "$subdir" =~ ^.*\.git$ ]]; then - create_link_in "$src/$dir/$subdir" - fi - done - fi - fi -done +gitlab_shell_dir="$(cd $(dirname $0) && pwd)/.." +exec ${gitlab_shell_dir}/bin/create-hooks |