internal_spec.rb 15.2 KB
Newer Older
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
1 2
require 'spec_helper'

3
describe API::Internal, api: true  do
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
4 5 6 7
  include ApiHelpers
  let(:user) { create(:user) }
  let(:key) { create(:key, user: user) }
  let(:project) { create(:project) }
8
  let(:secret_token) { Gitlab::Shell.secret_token }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
9 10 11

  describe "GET /internal/check", no_db: true do
    it do
12
      get api("/internal/check"), secret_token: secret_token
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
13

14
      expect(response).to have_http_status(200)
15
      expect(json_response['api_version']).to eq(API::API.version)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
16 17 18
    end
  end

19 20 21 22 23 24 25
  describe "GET /internal/broadcast_message" do
    context "broadcast message exists" do
      let!(:broadcast_message) { create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow ) }

      it do
        get api("/internal/broadcast_message"), secret_token: secret_token

26
        expect(response).to have_http_status(200)
27
        expect(json_response["message"]).to eq(broadcast_message.message)
28 29 30 31 32 33 34
      end
    end

    context "broadcast message doesn't exist" do
      it do
        get api("/internal/broadcast_message"), secret_token: secret_token

35
        expect(response).to have_http_status(200)
36
        expect(json_response).to be_empty
37 38 39 40
      end
    end
  end

41 42 43 44 45 46
  describe 'GET /internal/two_factor_recovery_codes' do
    it 'returns an error message when the key does not exist' do
      post api('/internal/two_factor_recovery_codes'),
           secret_token: secret_token,
           key_id: 12345

47 48
      expect(json_response['success']).to be_falsey
      expect(json_response['message']).to eq('Could not find the given key')
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
    end

    it 'returns an error message when the key is a deploy key' do
      deploy_key = create(:deploy_key)

      post api('/internal/two_factor_recovery_codes'),
           secret_token: secret_token,
           key_id: deploy_key.id

      expect(json_response['success']).to be_falsey
      expect(json_response['message']).to eq('Deploy keys cannot be used to retrieve recovery codes')
    end

    it 'returns an error message when the user does not exist' do
      key_without_user = create(:key, user: nil)

      post api('/internal/two_factor_recovery_codes'),
           secret_token: secret_token,
           key_id: key_without_user.id

      expect(json_response['success']).to be_falsey
      expect(json_response['message']).to eq('Could not find a user for the given key')
      expect(json_response['recovery_codes']).to be_nil
    end

    context 'when two-factor is enabled' do
      it 'returns new recovery codes when the user exists' do
        allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(true)
        allow_any_instance_of(User)
          .to receive(:generate_otp_backup_codes!).and_return(%w(119135e5a3ebce8e 34bd7b74adbc8861))

        post api('/internal/two_factor_recovery_codes'),
             secret_token: secret_token,
             key_id: key.id

        expect(json_response['success']).to be_truthy
        expect(json_response['recovery_codes']).to match_array(%w(119135e5a3ebce8e 34bd7b74adbc8861))
      end
    end

    context 'when two-factor is not enabled' do
      it 'returns an error message' do
        allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(false)

        post api('/internal/two_factor_recovery_codes'),
             secret_token: secret_token,
             key_id: key.id

        expect(json_response['success']).to be_falsey
        expect(json_response['recovery_codes']).to be_nil
      end
    end
  end

103 104 105 106 107 108 109 110 111 112 113
  describe "POST /internal/lfs_authenticate" do
    before do
      project.team << [user, :developer]
    end

    context 'user key' do
      it 'returns the correct information about the key' do
        lfs_auth(key.id, project)

        expect(response).to have_http_status(200)
        expect(json_response['username']).to eq(user.username)
114
        expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token)
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133

        expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
      end

      it 'returns a 404 when the wrong key is provided' do
        lfs_auth(nil, project)

        expect(response).to have_http_status(404)
      end
    end

    context 'deploy key' do
      let(:key) { create(:deploy_key) }

      it 'returns the correct information about the key' do
        lfs_auth(key.id, project)

        expect(response).to have_http_status(200)
        expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}")
134
        expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token)
135 136 137 138 139
        expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
      end
    end
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
140 141
  describe "GET /internal/discover" do
    it do
142
      get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
143

144
      expect(response).to have_http_status(200)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
145

146
      expect(json_response['name']).to eq(user.name)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
147 148 149
    end
  end

150
  describe "GET /internal/authorized_keys" do
151
    context "unsing an existing key's fingerprint" do
152
      it "finds the key" do
153
        get(api('/internal/authorized_keys'), fingerprint: key.fingerprint, secret_token: secret_token)
154 155 156 157 158 159

        expect(response.status).to eq(200)
        expect(json_response["key"]).to eq(key.key)
      end
    end

160
    context "non existing key's fingerprint" do
161
      it "returns 404" do
162
        get(api('/internal/authorized_keys'), fingerprint: "no:t-:va:li:d0", secret_token: secret_token)
163 164 165 166 167

        expect(response.status).to eq(404)
      end
    end

168
    context "using a partial fingerprint" do
169
      it "returns 404" do
170
        get(api('/internal/authorized_keys'), fingerprint: "#{key.fingerprint[0..5]}%", secret_token: secret_token)
171 172 173 174

        expect(response.status).to eq(404)
      end
    end
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195

    context "sending the key" do
      it "finds the key" do
        get(api('/internal/authorized_keys'), key: key.key.split[1], secret_token: secret_token)

        expect(response.status).to eq(200)
        expect(json_response["key"]).to eq(key.key)
      end

      it "returns 404 with a partial key" do
        get(api('/internal/authorized_keys'), key: key.key.split[1][0...-3], secret_token: secret_token)

        expect(response.status).to eq(404)
      end

      it "returns 404 with an not valid base64 string" do
        get(api('/internal/authorized_keys'), key: "whatever!", secret_token: secret_token)

        expect(response.status).to eq(404)
      end
    end
196 197
  end

198 199 200
  describe "POST /internal/allowed", :redis do
    include UserActivitiesHelpers

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
201 202 203
    context "access granted" do
      before do
        project.team << [user, :developer]
204 205 206 207 208
        Timecop.freeze
      end

      after do
        Timecop.return
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
209 210
      end

211
      context "git push with project.wiki" do
212
        it 'responds with success' do
213
          push(key, project.wiki)
214

215 216 217
          expect(response).to have_http_status(200)
          expect(json_response["status"]).to be_truthy
          expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
218
          expect(user_score).to be_zero
219 220 221 222 223 224
        end
      end

      context "git pull with project.wiki" do
        it 'responds with success' do
          pull(key, project.wiki)
225

226
          expect(response).to have_http_status(200)
227
          expect(json_response["status"]).to be_truthy
228
          expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
229
          expect(user_score).not_to be_zero
230 231 232
        end
      end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
233 234
      context "git pull" do
        it do
235
          pull(key, project)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
236

237
          expect(response).to have_http_status(200)
238
          expect(json_response["status"]).to be_truthy
239
          expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
240
          expect(user_score).not_to be_zero
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
241 242 243 244 245
        end
      end

      context "git push" do
        it do
246
          push(key, project)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
247

248
          expect(response).to have_http_status(200)
249
          expect(json_response["status"]).to be_truthy
250
          expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
251
          expect(user_score).to be_zero
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
252
        end
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

        context 'project as /namespace/project' do
          it do
            pull(key, project_with_repo_path('/' + project.path_with_namespace))

            expect(response).to have_http_status(200)
            expect(json_response["status"]).to be_truthy
            expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
          end
        end

        context 'project as namespace/project' do
          it do
            pull(key, project_with_repo_path(project.path_with_namespace))

            expect(response).to have_http_status(200)
            expect(json_response["status"]).to be_truthy
            expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
          end
        end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
273 274 275 276 277 278 279 280 281 282
      end
    end

    context "access denied" do
      before do
        project.team << [user, :guest]
      end

      context "git pull" do
        it do
283
          pull(key, project)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
284

285
          expect(response).to have_http_status(200)
286
          expect(json_response["status"]).to be_falsey
287
          expect(user_score).to be_zero
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
288 289 290 291 292
        end
      end

      context "git push" do
        it do
293
          push(key, project)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
294

295
          expect(response).to have_http_status(200)
296
          expect(json_response["status"]).to be_falsey
297
          expect(user_score).to be_zero
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
298 299 300 301
        end
      end
    end

302
    context "blocked user" do
303
      let(:personal_project) { create(:empty_project, namespace: user.namespace) }
304 305 306 307 308 309 310 311 312

      before do
        user.block
      end

      context "git pull" do
        it do
          pull(key, personal_project)

313
          expect(response).to have_http_status(200)
314
          expect(json_response["status"]).to be_falsey
315
          expect(user_score).to be_zero
316 317 318 319 320 321 322
        end
      end

      context "git push" do
        it do
          push(key, personal_project)

323
          expect(response).to have_http_status(200)
324
          expect(json_response["status"]).to be_falsey
325
          expect(user_score).to be_zero
326 327 328
        end
      end
    end
329

330
    context "archived project" do
331
      let(:personal_project) { create(:empty_project, namespace: user.namespace) }
332 333 334 335 336 337 338 339 340 341

      before do
        project.team << [user, :developer]
        project.archive!
      end

      context "git pull" do
        it do
          pull(key, project)

342
          expect(response).to have_http_status(200)
343
          expect(json_response["status"]).to be_truthy
344 345 346 347 348 349 350
        end
      end

      context "git push" do
        it do
          push(key, project)

351
          expect(response).to have_http_status(200)
352
          expect(json_response["status"]).to be_falsey
353 354 355 356
        end
      end
    end

357 358 359 360 361 362 363 364 365 366 367
    context "deploy key" do
      let(:key) { create(:deploy_key) }

      context "added to project" do
        before do
          key.projects << project
        end

        it do
          archive(key, project)

368
          expect(response).to have_http_status(200)
369
          expect(json_response["status"]).to be_truthy
370 371 372 373 374 375 376
        end
      end

      context "not added to project" do
        it do
          archive(key, project)

377
          expect(response).to have_http_status(200)
378
          expect(json_response["status"]).to be_falsey
379 380 381
        end
      end
    end
382 383 384

    context 'project does not exist' do
      it do
385
        pull(key, project_with_repo_path('gitlab/notexist'))
386

387
        expect(response).to have_http_status(200)
388
        expect(json_response["status"]).to be_falsey
389 390 391 392 393 394 395
      end
    end

    context 'user does not exist' do
      it do
        pull(OpenStruct.new(id: 0), project)

396
        expect(response).to have_http_status(200)
397
        expect(json_response["status"]).to be_falsey
398 399
      end
    end
400 401 402

    context 'ssh access has been disabled' do
      before do
403
        stub_application_setting(enabled_git_access_protocol: 'http')
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
      end

      it 'rejects the SSH push' do
        push(key, project)

        expect(response.status).to eq(200)
        expect(json_response['status']).to be_falsey
        expect(json_response['message']).to eq 'Git access over SSH is not allowed'
      end

      it 'rejects the SSH pull' do
        pull(key, project)

        expect(response.status).to eq(200)
        expect(json_response['status']).to be_falsey
        expect(json_response['message']).to eq 'Git access over SSH is not allowed'
      end
    end

    context 'http access has been disabled' do
      before do
425
        stub_application_setting(enabled_git_access_protocol: 'ssh')
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
      end

      it 'rejects the HTTP push' do
        push(key, project, 'http')

        expect(response.status).to eq(200)
        expect(json_response['status']).to be_falsey
        expect(json_response['message']).to eq 'Git access over HTTP is not allowed'
      end

      it 'rejects the HTTP pull' do
        pull(key, project, 'http')

        expect(response.status).to eq(200)
        expect(json_response['status']).to be_falsey
        expect(json_response['message']).to eq 'Git access over HTTP is not allowed'
      end
    end

    context 'web actions are always allowed' do
      it 'allows WEB push' do
447
        stub_application_setting(enabled_git_access_protocol: 'ssh')
448 449 450 451 452 453 454
        project.team << [user, :developer]
        push(key, project, 'web')

        expect(response.status).to eq(200)
        expect(json_response['status']).to be_truthy
      end
    end
455 456
  end

457 458 459 460 461 462 463 464 465 466 467 468
  describe 'GET /internal/merge_request_urls' do
    let(:repo_name) { "#{project.namespace.name}/#{project.path}" }
    let(:changes) { URI.escape("#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch") }

    before do
      project.team << [user, :developer]
      get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), secret_token: secret_token
    end

    it 'returns link to create new merge request' do
      expect(json_response).to match [{
        "branch_name" => "new_branch",
469
        "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
470 471 472 473 474
        "new_merge_request" => true
      }]
    end
  end

475 476 477 478 479 480
  def project_with_repo_path(path)
    double().tap do |fake_project|
      allow(fake_project).to receive_message_chain('repository.path_to_repo' => path)
    end
  end

481
  def pull(key, project, protocol = 'ssh')
482
    post(
483 484
      api("/internal/allowed"),
      key_id: key.id,
485
      project: project.repository.path_to_repo,
486
      action: 'git-upload-pack',
487 488
      secret_token: secret_token,
      protocol: protocol
489 490 491
    )
  end

492
  def push(key, project, protocol = 'ssh')
493
    post(
494
      api("/internal/allowed"),
495
      changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master',
496
      key_id: key.id,
497
      project: project.repository.path_to_repo,
498
      action: 'git-receive-pack',
499 500
      secret_token: secret_token,
      protocol: protocol
501
    )
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
502
  end
503 504

  def archive(key, project)
505
    post(
506 507 508
      api("/internal/allowed"),
      ref: 'master',
      key_id: key.id,
509
      project: project.repository.path_to_repo,
510
      action: 'git-upload-archive',
511 512
      secret_token: secret_token,
      protocol: 'ssh'
513 514
    )
  end
515 516 517 518 519 520

  def lfs_auth(key_id, project)
    post(
      api("/internal/lfs_authenticate"),
      key_id: key_id,
      secret_token: secret_token,
521
      project: project.repository.path_to_repo
522 523
    )
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
524
end