Skip to content
Snippets Groups Projects
git_http_spec.rb 9.76 KiB
Newer Older
  • Learn to ignore specific revisions
  • Douwe Maan's avatar
    Douwe Maan committed
    require "spec_helper"
    
    
    describe 'Git HTTP requests', lib: true do
    
    Douwe Maan's avatar
    Douwe Maan committed
      let(:user)    { create(:user) }
      let(:project) { create(:project) }
    
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
      it "gives WWW-Authenticate hints" do
        clone_get('doesnt/exist.git')
    
        expect(response.header['WWW-Authenticate']).to start_with('Basic ')
      end
    
    
      context "when the project doesn't exist" do
        context "when no authentication is provided" do
    
          it "responds with status 401 (no project existence information leak)" do
    
            download('doesnt/exist.git') do |response|
              expect(response.status).to eq(401)
    
    Douwe Maan's avatar
    Douwe Maan committed
            end
          end
    
        context "when username and password are provided" do
          context "when authentication fails" do
            it "responds with status 401" do
              download('doesnt/exist.git', user: user.username, password: "nope") do |response|
                expect(response.status).to eq(401)
    
    Douwe Maan's avatar
    Douwe Maan committed
              end
            end
    
          context "when authentication succeeds" do
            it "responds with status 404" do
              download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
                expect(response.status).to eq(404)
    
    Douwe Maan's avatar
    Douwe Maan committed
              end
            end
          end
        end
    
      context "when the Wiki for a project exists" do
        it "responds with the right project" do
          wiki = ProjectWiki.new(project)
          project.update_attribute(:visibility_level, Project::PUBLIC)
    
          download("/#{wiki.repository.path_with_namespace}.git") do |response|
            json_body = ActiveSupport::JSON.decode(response.body)
    
            expect(response.status).to eq(200)
            expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
    
      context "when the project exists" do
        let(:path) { "#{project.path_with_namespace}.git" }
    
        context "when the project is public" do
          before do
            project.update_attribute(:visibility_level, Project::PUBLIC)
          end
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
    
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
          it "downloads get status 200" do
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
            download(path, {}) do |response|
    
              expect(response.status).to eq(200)
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
    
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
          it "uploads get status 401" do
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
            upload(path, {}) do |response|
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
              expect(response.status).to eq(401)
            end
          end
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
          context "with correct credentials" do
            let(:env) { { user: user.username, password: user.password } }
    
            it "uploads get status 200 (because Git hooks do the real check)" do
              upload(path, env) do |response|
                expect(response.status).to eq(200)
              end
            end
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
            context 'but git-receive-pack is disabled' do
              it "responds with status 404" do
                allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
                upload(path, env) do |response|
                  expect(response.status).to eq(404)
                end
              end
            end
          end
    
    
          context 'but git-upload-pack is disabled' do
            it "responds with status 404" do
              allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
    
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
              download(path, {}) do |response|
    
                expect(response.status).to eq(404)
    
    Douwe Maan's avatar
    Douwe Maan committed
            end
    
        context "when the project is private" do
          before do
            project.update_attribute(:visibility_level, Project::PRIVATE)
          end
    
          context "when no authentication is provided" do
    
            it "responds with status 401 to downloads" do
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
              download(path, {}) do |response|
    
                expect(response.status).to eq(401)
    
    
            it "responds with status 401 to uploads" do
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
              upload(path, {}) do |response|
    
                expect(response.status).to eq(401)
              end
            end
    
          context "when username and password are provided" do
            let(:env) { { user: user.username, password: 'nope' } }
    
            context "when authentication fails" do
    
    Douwe Maan's avatar
    Douwe Maan committed
              it "responds with status 401" do
    
                download(path, env) do |response|
                  expect(response.status).to eq(401)
                end
    
              context "when the user is IP banned" do
    
    Douwe Maan's avatar
    Douwe Maan committed
                it "responds with status 401" do
    
                  expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
                  allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4')
    
                  clone_get(path, env)
    
                  expect(response.status).to eq(401)
    
    Douwe Maan's avatar
    Douwe Maan committed
              end
    
            context "when authentication succeeds" do
              let(:env) { { user: user.username, password: user.password } }
    
              context "when the user has access to the project" do
                before do
                  project.team << [user, :master]
                end
    
                context "when the user is blocked" do
                  it "responds with status 404" do
                    user.block
                    project.team << [user, :master]
    
                    download(path, env) do |response|
                      expect(response.status).to eq(404)
    
    Douwe Maan's avatar
    Douwe Maan committed
                    end
                  end
    
                context "when the user isn't blocked" do
    
                  it "downloads get status 200" do
    
                    expect(Rack::Attack::Allow2Ban).to receive(:reset)
    
                    clone_get(path, env)
    
                    expect(response.status).to eq(200)
    
    Douwe Maan's avatar
    Douwe Maan committed
                  end
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
                  it "uploads get status 200" do
                    upload(path, env) do |response|
                      expect(response.status).to eq(200)
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
                  end
    
                context "when an oauth token is provided" do
                  before do
                    application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
                    @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id)
                  end
    
                  it "downloads get status 200" do
                    clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
    
                    expect(response.status).to eq(200)
                  end
    
                  it "uploads get status 401 (no project existence information leak)" do
                    push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
    
                    expect(response.status).to eq(401)
                  end
                end
    
    
                context "when blank password attempts follow a valid login" do
                  def attempt_login(include_password)
                    password = include_password ? user.password : ""
                    clone_get path, user: user.username, password: password
                    response.status
                  end
    
                  it "repeated attempts followed by successful attempt" do
                    options = Gitlab.config.rack_attack.git_basic_auth
                    maxretry = options[:maxretry] - 1
                    ip = '1.2.3.4'
    
                    allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
                    Rack::Attack::Allow2Ban.reset(ip, options)
    
                    maxretry.times.each do
                      expect(attempt_login(false)).to eq(401)
                    end
    
                    expect(attempt_login(true)).to eq(200)
                    expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
    
                    maxretry.times.each do
                      expect(attempt_login(false)).to eq(401)
    
    
                    Rack::Attack::Allow2Ban.reset(ip, options)
    
    Douwe Maan's avatar
    Douwe Maan committed
                end
    
              context "when the user doesn't have access to the project" do
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
                it "downloads get status 404" do
    
                  download(path, user: user.username, password: user.password) do |response|
                    expect(response.status).to eq(404)
    
    Douwe Maan's avatar
    Douwe Maan committed
                  end
                end
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
                it "uploads get status 200 (because Git hooks do the real check)" do
                  upload(path, user: user.username, password: user.password) do |response|
                    expect(response.status).to eq(200)
                  end
                end
    
    Douwe Maan's avatar
    Douwe Maan committed
              end
            end
    
          context "when a gitlab ci token is provided" do
    
            let(:token) { 123 }
            let(:project) { FactoryGirl.create :empty_project }
    
            before do
    
              project.update_attributes(runners_token: token, builds_enabled: true)
    
            it "downloads get status 200" do
    
              clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
    
              expect(response.status).to eq(200)
    
    Douwe Maan's avatar
    Douwe Maan committed
            end
    
    
            it "uploads get status 401 (no project existence information leak)" do
              push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
    
              expect(response.status).to eq(401)
            end
    
    Douwe Maan's avatar
    Douwe Maan committed
          end
        end
      end
    
      def clone_get(project, options={})
        get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password))
      end
    
      def clone_post(project, options={})
        post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password))
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
      def push_get(project, options={})
        get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password))
      end
    
      def push_post(project, options={})
        post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password))
      end
    
    
      def download(project, user: nil, password: nil)
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
        args = [project, { user: user, password: password }]
    
    
        clone_get *args
        yield response
    
        clone_post *args
        yield response
      end
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
      def upload(project, user: nil, password: nil)
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
        args = [project, { user: user, password: password }]
    
    Jacob Vosmaer's avatar
    Jacob Vosmaer committed
    
        push_get *args
        yield response
    
        push_post *args
        yield response
      end
    
    
      def auth_env(user, password)
        if user && password
          { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) }
        else
          {}
        end
      end
    
    Douwe Maan's avatar
    Douwe Maan committed
    end