git_http_spec.rb 9.76 KB
Newer Older
Douwe Maan's avatar
Douwe Maan committed
1 2
require "spec_helper"

3
describe 'Git HTTP requests', lib: true do
Douwe Maan's avatar
Douwe Maan committed
4 5 6
  let(:user)    { create(:user) }
  let(:project) { create(:project) }

Jacob Vosmaer's avatar
Jacob Vosmaer committed
7 8 9 10 11 12
  it "gives WWW-Authenticate hints" do
    clone_get('doesnt/exist.git')

    expect(response.header['WWW-Authenticate']).to start_with('Basic ')
  end

Jacob Vosmaer's avatar
Jacob Vosmaer committed
13 14
  context "when the project doesn't exist" do
    context "when no authentication is provided" do
15
      it "responds with status 401 (no project existence information leak)" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
16 17
        download('doesnt/exist.git') do |response|
          expect(response.status).to eq(401)
Douwe Maan's avatar
Douwe Maan committed
18 19
        end
      end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
20
    end
Douwe Maan's avatar
Douwe Maan committed
21

Jacob Vosmaer's avatar
Jacob Vosmaer committed
22 23 24 25 26
    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
27 28
          end
        end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
29
      end
Douwe Maan's avatar
Douwe Maan committed
30

Jacob Vosmaer's avatar
Jacob Vosmaer committed
31 32 33 34
      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
35 36 37 38
          end
        end
      end
    end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
39
  end
Douwe Maan's avatar
Douwe Maan committed
40

Jacob Vosmaer's avatar
Jacob Vosmaer committed
41 42 43 44
  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)
45

Jacob Vosmaer's avatar
Jacob Vosmaer committed
46 47
      download("/#{wiki.repository.path_with_namespace}.git") do |response|
        json_body = ActiveSupport::JSON.decode(response.body)
48

Jacob Vosmaer's avatar
Jacob Vosmaer committed
49 50
        expect(response.status).to eq(200)
        expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
51 52
      end
    end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
53
  end
54

Jacob Vosmaer's avatar
Jacob Vosmaer committed
55 56
  context "when the project exists" do
    let(:path) { "#{project.path_with_namespace}.git" }
Douwe Maan's avatar
Douwe Maan committed
57

Jacob Vosmaer's avatar
Jacob Vosmaer committed
58 59 60 61
    context "when the project is public" do
      before do
        project.update_attribute(:visibility_level, Project::PUBLIC)
      end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
62

Jacob Vosmaer's avatar
Jacob Vosmaer committed
63
      it "downloads get status 200" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
64
        download(path, {}) do |response|
Jacob Vosmaer's avatar
Jacob Vosmaer committed
65
          expect(response.status).to eq(200)
66
        end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
67
      end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
68

Jacob Vosmaer's avatar
Jacob Vosmaer committed
69
      it "uploads get status 401" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
70
        upload(path, {}) do |response|
Jacob Vosmaer's avatar
Jacob Vosmaer committed
71 72 73
          expect(response.status).to eq(401)
        end
      end
74

Jacob Vosmaer's avatar
Jacob Vosmaer committed
75 76 77 78 79 80 81 82
      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
83

Jacob Vosmaer's avatar
Jacob Vosmaer committed
84 85 86
        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)
87

Jacob Vosmaer's avatar
Jacob Vosmaer committed
88 89 90 91 92 93 94
            upload(path, env) do |response|
              expect(response.status).to eq(404)
            end
          end
        end
      end

Jacob Vosmaer's avatar
Jacob Vosmaer committed
95 96 97 98
      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
99
          download(path, {}) do |response|
Jacob Vosmaer's avatar
Jacob Vosmaer committed
100
            expect(response.status).to eq(404)
101
          end
Douwe Maan's avatar
Douwe Maan committed
102
        end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
103 104
      end
    end
105

Jacob Vosmaer's avatar
Jacob Vosmaer committed
106 107 108 109 110 111
    context "when the project is private" do
      before do
        project.update_attribute(:visibility_level, Project::PRIVATE)
      end

      context "when no authentication is provided" do
112
        it "responds with status 401 to downloads" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
113
          download(path, {}) do |response|
Jacob Vosmaer's avatar
Jacob Vosmaer committed
114
            expect(response.status).to eq(401)
115 116
          end
        end
117 118

        it "responds with status 401 to uploads" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
119
          upload(path, {}) do |response|
120 121 122
            expect(response.status).to eq(401)
          end
        end
Douwe Maan's avatar
Douwe Maan committed
123 124
      end

Jacob Vosmaer's avatar
Jacob Vosmaer committed
125 126
      context "when username and password are provided" do
        let(:env) { { user: user.username, password: 'nope' } }
Douwe Maan's avatar
Douwe Maan committed
127

Jacob Vosmaer's avatar
Jacob Vosmaer committed
128
        context "when authentication fails" do
Douwe Maan's avatar
Douwe Maan committed
129
          it "responds with status 401" do
130 131 132
            download(path, env) do |response|
              expect(response.status).to eq(401)
            end
Douwe Maan's avatar
Douwe Maan committed
133 134
          end

Jacob Vosmaer's avatar
Jacob Vosmaer committed
135
          context "when the user is IP banned" do
Douwe Maan's avatar
Douwe Maan committed
136
            it "responds with status 401" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
137 138
              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')
139

Jacob Vosmaer's avatar
Jacob Vosmaer committed
140
              clone_get(path, env)
141

Jacob Vosmaer's avatar
Jacob Vosmaer committed
142
              expect(response.status).to eq(401)
143
            end
Douwe Maan's avatar
Douwe Maan committed
144
          end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
145
        end
Douwe Maan's avatar
Douwe Maan committed
146

Jacob Vosmaer's avatar
Jacob Vosmaer committed
147 148
        context "when authentication succeeds" do
          let(:env) { { user: user.username, password: user.password } }
149

Jacob Vosmaer's avatar
Jacob Vosmaer committed
150 151 152 153
          context "when the user has access to the project" do
            before do
              project.team << [user, :master]
            end
Douwe Maan's avatar
Douwe Maan committed
154

Jacob Vosmaer's avatar
Jacob Vosmaer committed
155 156 157 158
            context "when the user is blocked" do
              it "responds with status 404" do
                user.block
                project.team << [user, :master]
Douwe Maan's avatar
Douwe Maan committed
159

Jacob Vosmaer's avatar
Jacob Vosmaer committed
160 161
                download(path, env) do |response|
                  expect(response.status).to eq(404)
Douwe Maan's avatar
Douwe Maan committed
162 163
                end
              end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
164
            end
Douwe Maan's avatar
Douwe Maan committed
165

Jacob Vosmaer's avatar
Jacob Vosmaer committed
166
            context "when the user isn't blocked" do
167
              it "downloads get status 200" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
168
                expect(Rack::Attack::Allow2Ban).to receive(:reset)
169

Jacob Vosmaer's avatar
Jacob Vosmaer committed
170
                clone_get(path, env)
171

Jacob Vosmaer's avatar
Jacob Vosmaer committed
172
                expect(response.status).to eq(200)
Douwe Maan's avatar
Douwe Maan committed
173
              end
174

Jacob Vosmaer's avatar
Jacob Vosmaer committed
175 176 177
              it "uploads get status 200" do
                upload(path, env) do |response|
                  expect(response.status).to eq(200)
178
                end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
179
              end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
180
            end
181

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
            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

Jacob Vosmaer's avatar
Jacob Vosmaer committed
201 202 203 204 205 206
            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
207

Jacob Vosmaer's avatar
Jacob Vosmaer committed
208 209 210 211
              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'
212

Jacob Vosmaer's avatar
Jacob Vosmaer committed
213 214
                allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
                Rack::Attack::Allow2Ban.reset(ip, options)
215

Jacob Vosmaer's avatar
Jacob Vosmaer committed
216 217 218
                maxretry.times.each do
                  expect(attempt_login(false)).to eq(401)
                end
219

Jacob Vosmaer's avatar
Jacob Vosmaer committed
220 221
                expect(attempt_login(true)).to eq(200)
                expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
222

Jacob Vosmaer's avatar
Jacob Vosmaer committed
223 224
                maxretry.times.each do
                  expect(attempt_login(false)).to eq(401)
225
                end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
226 227

                Rack::Attack::Allow2Ban.reset(ip, options)
228
              end
Douwe Maan's avatar
Douwe Maan committed
229
            end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
230
          end
Douwe Maan's avatar
Douwe Maan committed
231

Jacob Vosmaer's avatar
Jacob Vosmaer committed
232
          context "when the user doesn't have access to the project" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
233
            it "downloads get status 404" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
234 235
              download(path, user: user.username, password: user.password) do |response|
                expect(response.status).to eq(404)
Douwe Maan's avatar
Douwe Maan committed
236 237
              end
            end
238

Jacob Vosmaer's avatar
Jacob Vosmaer committed
239 240 241 242 243
            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
244 245
          end
        end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
246
      end
Douwe Maan's avatar
Douwe Maan committed
247

Jacob Vosmaer's avatar
Jacob Vosmaer committed
248
      context "when a gitlab ci token is provided" do
249 250 251 252
        let(:token) { 123 }
        let(:project) { FactoryGirl.create :empty_project }

        before do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
253
          project.update_attributes(runners_token: token, builds_enabled: true)
254
        end
Douwe Maan's avatar
Douwe Maan committed
255

256
        it "downloads get status 200" do
Jacob Vosmaer's avatar
Jacob Vosmaer committed
257
          clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
Douwe Maan's avatar
Douwe Maan committed
258

Jacob Vosmaer's avatar
Jacob Vosmaer committed
259
          expect(response.status).to eq(200)
Douwe Maan's avatar
Douwe Maan committed
260
        end
261 262 263 264 265 266

        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
267 268 269
      end
    end
  end
Jacob Vosmaer's avatar
Jacob Vosmaer committed
270

271 272 273
  def clone_get(project, options={})
    get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password))
  end
274

275 276
  def clone_post(project, options={})
    post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password))
277 278
  end

Jacob Vosmaer's avatar
Jacob Vosmaer committed
279 280 281 282 283 284 285 286
  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

287
  def download(project, user: nil, password: nil)
Jacob Vosmaer's avatar
Jacob Vosmaer committed
288
    args = [project, { user: user, password: password }]
289

Jacob Vosmaer's avatar
Jacob Vosmaer committed
290
    clone_get(*args)
291
    yield response
292

Jacob Vosmaer's avatar
Jacob Vosmaer committed
293
    clone_post(*args)
294 295
    yield response
  end
296

Jacob Vosmaer's avatar
Jacob Vosmaer committed
297
  def upload(project, user: nil, password: nil)
Jacob Vosmaer's avatar
Jacob Vosmaer committed
298
    args = [project, { user: user, password: password }]
Jacob Vosmaer's avatar
Jacob Vosmaer committed
299

Jacob Vosmaer's avatar
Jacob Vosmaer committed
300
    push_get(*args)
Jacob Vosmaer's avatar
Jacob Vosmaer committed
301 302
    yield response

Jacob Vosmaer's avatar
Jacob Vosmaer committed
303
    push_post(*args)
Jacob Vosmaer's avatar
Jacob Vosmaer committed
304 305 306
    yield response
  end

307 308 309 310 311 312 313
  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
314
end