diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index fee0a2788b2..227cea21564 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -1.9.7 +2.0.0 diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index f110e20bf00..1406cba2db3 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -4,8 +4,7 @@ class PostReceive sidekiq_options queue: :post_receive - def perform(repo_path, oldrev, newrev, ref, identifier) - + def perform(repo_path, identifier, changes) if repo_path.start_with?(Gitlab.config.gitlab_shell.repos_path.to_s) repo_path.gsub!(Gitlab.config.gitlab_shell.repos_path.to_s, "") else @@ -22,17 +21,23 @@ class PostReceive return false end - user = identify(identifier, project, newrev) + changes = changes.lines if changes.kind_of?(String) - unless user - log("Triggered hook for non-existing user \"#{identifier} \"") - return false - end + changes.each do |change| + oldrev, newrev, ref = change.strip.split(' ') - if tag?(ref) - GitTagPushService.new.execute(project, user, oldrev, newrev, ref) - else - GitPushService.new.execute(project, user, oldrev, newrev, ref) + @user ||= identify(identifier, project, newrev) + + unless @user + log("Triggered hook for non-existing user \"#{identifier} \"") + return false + end + + if tag?(ref) + GitTagPushService.new.execute(project, @user, oldrev, newrev, ref) + else + GitPushService.new.execute(project, @user, oldrev, newrev, ref) + end end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 5850892df07..86fa149d050 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -34,10 +34,7 @@ module API actor, params[:action], project, - params[:ref], - params[:oldrev], - params[:newrev], - params[:forced_push] + params[:changes] ) end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 38b3d82e2f4..e75a5a1d62e 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,7 +5,7 @@ module Gitlab attr_reader :params, :project, :git_cmd, :user - def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil, forced_push = false) + def allowed?(actor, cmd, project, changes = nil) case cmd when *DOWNLOAD_COMMANDS if actor.is_a? User @@ -19,12 +19,12 @@ module Gitlab end when *PUSH_COMMANDS if actor.is_a? User - push_allowed?(actor, project, ref, oldrev, newrev, forced_push) + push_allowed?(actor, project, changes) elsif actor.is_a? DeployKey # Deploy key not allowed to push return false elsif actor.is_a? Key - push_allowed?(actor.user, project, ref, oldrev, newrev, forced_push) + push_allowed?(actor.user, project, changes) else raise 'Wrong actor' end @@ -41,13 +41,21 @@ module Gitlab end end - def push_allowed?(user, project, ref, oldrev, newrev, forced_push) - if user && user_allowed?(user) + def push_allowed?(user, project, changes) + return false unless user && user_allowed?(user) + return true if changes.blank? + + changes = changes.lines if changes.kind_of?(String) + + # Iterate over all changes to find if user allowed all of them to be applied + changes.each do |change| + oldrev, newrev, ref = changes.split('') + action = if project.protected_branch?(ref) # we dont allow force push to protected branch - if forced_push.to_s == 'true' + if forced_push?(oldrev, newrev) :force_push_code_to_protected_branches - # and we dont allow remove of protected branch + # and we dont allow remove of protected branch elsif newrev =~ /0000000/ :remove_protected_branches else @@ -59,7 +67,22 @@ module Gitlab else :push_code end - user.can?(action, project) + unless user.can?(action, project) + # If user does not have access to make at least one change - cancel all push + return false + end + end + + # If user has access to make all changes + true + end + + def forced_push?(oldrev, newrev) + return false if project.empty_repo? + + if oldrev !~ /00000000/ && newrev !~ /00000000/ + missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read + missed_refs.split("\n").size > 0 else false end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index dbe8043c633..47da82c061f 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -157,7 +157,6 @@ describe API::API, api: true do def pull(key, project) get( api("/internal/allowed"), - ref: 'master', key_id: key.id, project: project.path_with_namespace, action: 'git-upload-pack' @@ -167,7 +166,7 @@ describe API::API, api: true do def push(key, project) get( api("/internal/allowed"), - ref: 'master', + changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', key_id: key.id, project: project.path_with_namespace, action: 'git-receive-pack' diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index e6bf79b853c..4273fd1019a 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe PostReceive do - context "as a resque worker" do it "reponds to #perform" do PostReceive.new.should respond_to(:perform) @@ -15,7 +14,7 @@ describe PostReceive do it "fetches the correct project" do Project.should_receive(:find_with_namespace).with(project.path_with_namespace).and_return(project) - PostReceive.new.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id) + PostReceive.new.perform(pwd(project), key_id, changes) end it "does not run if the author is not in the project" do @@ -23,7 +22,7 @@ describe PostReceive do project.should_not_receive(:execute_hooks) - PostReceive.new.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false + PostReceive.new.perform(pwd(project), key_id, changes).should be_false end it "asks the project to trigger all hooks" do @@ -32,11 +31,15 @@ describe PostReceive do project.should_receive(:execute_services) project.should_receive(:update_merge_requests) - PostReceive.new.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id) + PostReceive.new.perform(pwd(project), key_id, changes) end end def pwd(project) File.join(Gitlab.config.gitlab_shell.repos_path, project.path_with_namespace) end + + def changes + 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master' + end end