BigW Consortium Gitlab

helpers_spec.rb 15.1 KB
Newer Older
1 2
require 'spec_helper'

3
describe API::Helpers do
4
  include API::APIGuard::HelperMethods
5
  include described_class
6
  include SentryHelper
7

8 9 10 11 12
  let(:user) { create(:user) }
  let(:admin) { create(:admin) }
  let(:key) { create(:key, user: user) }

  let(:params) { {} }
Douwe Maan committed
13 14 15 16 17 18 19 20 21 22
  let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) }
  let(:env) do
    {
      'rack.input' => '',
      'rack.session' => {
        _csrf_token: csrf_token
      },
      'REQUEST_METHOD' => 'GET'
    }
  end
Kamil Trzcinski committed
23
  let(:header) { }
24

25 26 27
  before do
    allow_any_instance_of(self.class).to receive(:options).and_return({})
  end
28

29
  def set_env(user_or_token, identifier)
30 31
    clear_env
    clear_param
32
    env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token
33
    env[API::Helpers::SUDO_HEADER] = identifier.to_s
34 35
  end

36
  def set_param(user_or_token, identifier)
37 38
    clear_env
    clear_param
39
    params[API::APIGuard::PRIVATE_TOKEN_PARAM] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token
40
    params[API::Helpers::SUDO_PARAM] = identifier.to_s
41 42 43
  end

  def clear_env
44
    env.delete(API::APIGuard::PRIVATE_TOKEN_HEADER)
45
    env.delete(API::Helpers::SUDO_HEADER)
46 47 48
  end

  def clear_param
49
    params.delete(API::APIGuard::PRIVATE_TOKEN_PARAM)
50
    params.delete(API::Helpers::SUDO_PARAM)
51 52
  end

53 54 55 56 57 58
  def warden_authenticate_returns(value)
    warden = double("warden", authenticate: value)
    env['warden'] = warden
  end

  def doorkeeper_guard_returns(value)
59
    allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { value }
60 61
  end

Kamil Trzcinski committed
62
  def error!(message, status, header)
63
    raise Exception.new("#{status} - #{message}")
64 65 66
  end

  describe ".current_user" do
67 68
    subject { current_user }

Douwe Maan committed
69
    describe "Warden authentication", :allow_forgery_protection do
70 71 72
      before do
        doorkeeper_guard_returns false
      end
73

74 75
      context "with invalid credentials" do
        context "GET request" do
76 77 78 79
          before do
            env['REQUEST_METHOD'] = 'GET'
          end

80 81
          it { is_expected.to be_nil }
        end
82 83
      end

84
      context "with valid credentials" do
85 86 87
        before do
          warden_authenticate_returns user
        end
88

89
        context "GET request" do
90 91 92 93
          before do
            env['REQUEST_METHOD'] = 'GET'
          end

94 95 96 97
          it { is_expected.to eq(user) }
        end

        context "HEAD request" do
98 99 100 101
          before do
            env['REQUEST_METHOD'] = 'HEAD'
          end

102 103 104 105
          it { is_expected.to eq(user) }
        end

        context "PUT request" do
106 107 108 109
          before do
            env['REQUEST_METHOD'] = 'PUT'
          end

Douwe Maan committed
110 111 112 113 114 115 116 117 118 119 120
          context 'without CSRF token' do
            it { is_expected.to be_nil }
          end

          context 'with CSRF token' do
            before do
              env['HTTP_X_CSRF_TOKEN'] = csrf_token
            end

            it { is_expected.to eq(user) }
          end
121 122 123
        end

        context "POST request" do
124 125 126 127
          before do
            env['REQUEST_METHOD'] = 'POST'
          end

Douwe Maan committed
128 129 130 131 132 133 134 135 136 137 138
          context 'without CSRF token' do
            it { is_expected.to be_nil }
          end

          context 'with CSRF token' do
            before do
              env['HTTP_X_CSRF_TOKEN'] = csrf_token
            end

            it { is_expected.to eq(user) }
          end
139 140 141
        end

        context "DELETE request" do
142 143 144 145
          before do
            env['REQUEST_METHOD'] = 'DELETE'
          end

Douwe Maan committed
146 147 148 149 150 151 152 153 154 155 156
          context 'without CSRF token' do
            it { is_expected.to be_nil }
          end

          context 'with CSRF token' do
            before do
              env['HTTP_X_CSRF_TOKEN'] = csrf_token
            end

            it { is_expected.to eq(user) }
          end
157
        end
158 159 160
      end
    end

161
    describe "when authenticating using a user's private token" do
162
      it "returns nil for an invalid token" do
163
        env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token'
164
        allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false }
165

166 167 168
        expect(current_user).to be_nil
      end

169
      it "returns nil for a user without access" do
170
        env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token
171
        allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
172

173 174
        expect(current_user).to be_nil
      end
175

176
      it "leaves user as is when sudo not specified" do
177
        env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token
178

179
        expect(current_user).to eq(user)
180

181
        clear_env
182

183
        params[API::APIGuard::PRIVATE_TOKEN_PARAM] = user.private_token
184

185 186
        expect(current_user).to eq(user)
      end
187 188
    end

189 190 191
    describe "when authenticating using a user's personal access tokens" do
      let(:personal_access_token) { create(:personal_access_token, user: user) }

192 193 194 195
      before do
        allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false }
      end

196
      it "returns nil for an invalid token" do
197
        env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token'
198

199 200 201
        expect(current_user).to be_nil
      end

202
      it "returns nil for a user without access" do
203
        env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
204
        allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
205

206 207 208
        expect(current_user).to be_nil
      end

209 210 211
      it "returns nil for a token without the appropriate scope" do
        personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
        env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
212

213 214 215
        expect(current_user).to be_nil
      end

216
      it "leaves user as is when sudo not specified" do
217
        env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
218 219
        expect(current_user).to eq(user)
        clear_env
220
        params[API::APIGuard::PRIVATE_TOKEN_PARAM] = personal_access_token.token
221

222 223 224 225 226
        expect(current_user).to eq(user)
      end

      it 'does not allow revoked tokens' do
        personal_access_token.revoke!
227
        env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
228

229 230 231 232 233
        expect(current_user).to be_nil
      end

      it 'does not allow expired tokens' do
        personal_access_token.update_attributes!(expires_at: 1.day.ago)
234
        env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
235

236 237
        expect(current_user).to be_nil
      end
238 239
    end

240 241 242 243 244 245
    context 'sudo usage' do
      context 'with admin' do
        context 'with header' do
          context 'with id' do
            it 'changes current_user to sudo' do
              set_env(admin, user.id)
246

247 248
              expect(current_user).to eq(user)
            end
249

250 251 252 253 254 255 256
            it 'memoize the current_user: sudo permissions are not run against the sudoed user' do
              set_env(admin, user.id)

              expect(current_user).to eq(user)
              expect(current_user).to eq(user)
            end

257 258
            it 'handles sudo to oneself' do
              set_env(admin, admin.id)
259

260 261
              expect(current_user).to eq(admin)
            end
262

263 264 265 266
            it 'throws an error when user cannot be found' do
              id = user.id + admin.id
              expect(user.id).not_to eq(id)
              expect(admin.id).not_to eq(id)
267

268
              set_env(admin, id)
269

270 271 272
              expect { current_user }.to raise_error(Exception)
            end
          end
273

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
          context 'with username' do
            it 'changes current_user to sudo' do
              set_env(admin, user.username)

              expect(current_user).to eq(user)
            end

            it 'handles sudo to oneself' do
              set_env(admin, admin.username)

              expect(current_user).to eq(admin)
            end

            it "throws an error when the user cannot be found for a given username" do
              username = "#{user.username}#{admin.username}"
              expect(user.username).not_to eq(username)
              expect(admin.username).not_to eq(username)

              set_env(admin, username)

              expect { current_user }.to raise_error(Exception)
            end
          end
        end

        context 'with param' do
          context 'with id' do
            it 'changes current_user to sudo' do
              set_param(admin, user.id)

              expect(current_user).to eq(user)
            end

            it 'handles sudo to oneself' do
              set_param(admin, admin.id)

              expect(current_user).to eq(admin)
            end

            it 'handles sudo to oneself using string' do
              set_env(admin, user.id.to_s)

              expect(current_user).to eq(user)
            end

            it 'throws an error when user cannot be found' do
              id = user.id + admin.id
              expect(user.id).not_to eq(id)
              expect(admin.id).not_to eq(id)
323

324
              set_param(admin, id)
325

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
              expect { current_user }.to raise_error(Exception)
            end
          end

          context 'with username' do
            it 'changes current_user to sudo' do
              set_param(admin, user.username)

              expect(current_user).to eq(user)
            end

            it 'handles sudo to oneself' do
              set_param(admin, admin.username)

              expect(current_user).to eq(admin)
            end

            it "throws an error when the user cannot be found for a given username" do
              username = "#{user.username}#{admin.username}"
              expect(user.username).not_to eq(username)
              expect(admin.username).not_to eq(username)

              set_param(admin, username)

              expect { current_user }.to raise_error(Exception)
            end
          end
        end
      end

      context 'with regular user' do
        context 'with env' do
          it 'changes current_user to sudo when admin and user id' do
            set_env(user, admin.id)

            expect { current_user }.to raise_error(Exception)
          end

          it 'changes current_user to sudo when admin and user username' do
            set_env(user, admin.username)

            expect { current_user }.to raise_error(Exception)
          end
        end

        context 'with params' do
          it 'changes current_user to sudo when admin and user id' do
            set_param(user, admin.id)

            expect { current_user }.to raise_error(Exception)
          end

          it 'changes current_user to sudo when admin and user username' do
            set_param(user, admin.username)

            expect { current_user }.to raise_error(Exception)
          end
        end
      end
385 386 387
    end
  end

388 389 390 391 392 393 394 395 396
  describe '.sudo?' do
    context 'when no sudo env or param is passed' do
      before do
        doorkeeper_guard_returns(nil)
      end

      it 'returns false' do
        expect(sudo?).to be_falsy
      end
397 398
    end

399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
    context 'when sudo env or param is passed', 'user is not an admin' do
      before do
        set_env(user, '123')
      end

      it 'returns an 403 Forbidden' do
        expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden  - Must be admin to use sudo"}'
      end
    end

    context 'when sudo env or param is passed', 'user is admin' do
      context 'personal access token is used' do
        before do
          personal_access_token = create(:personal_access_token, user: admin)
          set_env(personal_access_token.token, user.id)
        end

        it 'returns an 403 Forbidden' do
          expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden  - Private token must be specified in order to use sudo"}'
        end
      end

      context 'private access token is used' do
        before do
          set_env(admin.private_token, user.id)
        end

        it 'returns true' do
          expect(sudo?).to be_truthy
        end
      end
430 431
    end
  end
432

433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
  describe '.handle_api_exception' do
    before do
      allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true)
      allow_any_instance_of(self.class).to receive(:rack_response)
    end

    it 'does not report a MethodNotAllowed exception to Sentry' do
      exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' })
      allow(exception).to receive(:backtrace).and_return(caller)

      expect(Raven).not_to receive(:capture_exception).with(exception)

      handle_api_exception(exception)
    end

    it 'does report RuntimeError to Sentry' do
      exception = RuntimeError.new('test error')
      allow(exception).to receive(:backtrace).and_return(caller)

      expect_any_instance_of(self.class).to receive(:sentry_context)
      expect(Raven).to receive(:capture_exception).with(exception)

      handle_api_exception(exception)
    end
  end
458 459 460 461 462

  describe '.authenticate_non_get!' do
    %w[HEAD GET].each do |method_name|
      context "method is #{method_name}" do
        before do
463
          expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name))
464 465 466 467 468 469 470 471 472 473 474 475 476
        end

        it 'does not raise an error' do
          expect_any_instance_of(self.class).not_to receive(:authenticate!)

          expect { authenticate_non_get! }.not_to raise_error
        end
      end
    end

    %w[POST PUT PATCH DELETE].each do |method_name|
      context "method is #{method_name}" do
        before do
477
          expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name))
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
        end

        it 'calls authenticate!' do
          expect_any_instance_of(self.class).to receive(:authenticate!)

          authenticate_non_get!
        end
      end
    end
  end

  describe '.authenticate!' do
    context 'current_user is nil' do
      before do
        expect_any_instance_of(self.class).to receive(:current_user).and_return(nil)
493
        allow_any_instance_of(self.class).to receive(:initial_current_user).and_return(nil)
494 495 496 497 498 499 500 501
      end

      it 'returns a 401 response' do
        expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}'
      end
    end

    context 'current_user is present' do
502 503
      let(:user) { build(:user) }

504
      before do
505 506
        expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(user)
        expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(user)
507 508 509 510 511 512
      end

      it 'does not raise an error' do
        expect { authenticate! }.not_to raise_error
      end
    end
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534

    context 'current_user is blocked' do
      let(:user) { build(:user, :blocked) }

      before do
        expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(user)
      end

      it 'raises an error' do
        expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(user)

        expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}'
      end

      it "doesn't raise an error if an admin user is impersonating a blocked user (via sudo)" do
        admin_user = build(:user, :admin)

        expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(admin_user)

        expect { authenticate! }.not_to raise_error
      end
    end
535
  end
536
end