auth_spec.rb 11.3 KB
Newer Older
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
1 2
require 'spec_helper'

Douwe Maan's avatar
Douwe Maan committed
3
describe Gitlab::Auth, lib: true do
4
  let(:gl_auth) { described_class }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
5

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
  describe 'constants' do
    it 'API_SCOPES contains all scopes for API access' do
      expect(subject::API_SCOPES).to eq [:api, :read_user]
    end

    it 'OPENID_SCOPES contains all scopes for OpenID Connect' do
      expect(subject::OPENID_SCOPES).to eq [:openid]
    end

    it 'DEFAULT_SCOPES contains all default scopes' do
      expect(subject::DEFAULT_SCOPES).to eq [:api]
    end

    it 'OPTIONAL_SCOPES contains all non-default scopes' do
      expect(subject::OPTIONAL_SCOPES).to eq [:read_user, :openid]
    end
  end

24
  describe 'find_for_git_client' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
25 26 27 28 29 30 31 32 33 34 35 36
    context 'build token' do
      subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') }

      context 'for running build' do
        let!(:build) { create(:ci_build, :running) }
        let(:project) { build.project }

        before do
          expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token')
        end

        it 'recognises user-less build' do
37
          expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities))
Kamil Trzcinski's avatar
Kamil Trzcinski committed
38 39 40 41 42
        end

        it 'recognises user token' do
          build.update(user: create(:user))

43
          expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities))
Kamil Trzcinski's avatar
Kamil Trzcinski committed
44 45 46
        end
      end

47
      (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
48 49 50 51 52 53 54 55 56
        context "for #{build_status} build" do
          let!(:build) { create(:ci_build, status: build_status) }
          let(:project) { build.project }

          before do
            expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token')
          end

          it 'denies authentication' do
57
            expect(subject).to eq(Gitlab::Auth::Result.new)
58
          end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
59 60 61 62 63
        end
      end
    end

    it 'recognizes other ci services' do
64
      project = create(:empty_project)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
65 66
      project.create_drone_ci_service(active: true)
      project.drone_ci_service.update(token: 'token')
67

68 69
      expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'drone-ci-token')
      expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities))
70 71 72 73 74
    end

    it 'recognizes master passwords' do
      user = create(:user, password: 'password')

75 76
      expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
      expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
77 78
    end

Pawel Chojnacki's avatar
Pawel Chojnacki committed
79 80
    include_examples 'user login operation with unique ip limit' do
      let(:user) { create(:user, password: 'password') }
81

Pawel Chojnacki's avatar
Pawel Chojnacki committed
82
      def operation
83 84 85 86
        expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
      end
    end

87 88 89 90
    context 'while using LFS authenticate' do
      it 'recognizes user lfs tokens' do
        user = create(:user)
        token = Gitlab::LfsToken.new(user).token
91

92 93 94
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
        expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
      end
95

96 97 98
      it 'recognizes deploy key lfs tokens' do
        key = create(:deploy_key)
        token = Gitlab::LfsToken.new(key).token
99

100 101 102
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
        expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
      end
103

104
      it 'does not try password auth before oauth' do
105
        user = create(:user)
106 107 108
        token = Gitlab::LfsToken.new(user).token

        expect(gl_auth).not_to receive(:find_with_user_password)
109

110 111 112 113 114 115 116 117 118 119
        gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')
      end
    end

    context 'while using OAuth tokens as passwords' do
      let(:user) { create(:user) }
      let(:token_w_api_scope) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api') }
      let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }

      it 'succeeds for OAuth tokens with the `api` scope' do
120
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2')
121
        expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities))
122 123 124
      end

      it 'fails for OAuth tokens with other scopes' do
125
        token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'read_user')
126

127 128
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'oauth2')
        expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
129
      end
130 131 132 133 134 135

      it 'does not try password auth before oauth' do
        expect(gl_auth).not_to receive(:find_with_user_password)

        gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')
      end
136 137
    end

138 139
    context 'while using personal access tokens as passwords' do
      it 'succeeds for personal access tokens with the `api` scope' do
Simon Vocella's avatar
Simon Vocella committed
140 141 142 143
        personal_access_token = create(:personal_access_token, scopes: ['api'])

        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
        expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, full_authentication_abilities))
144 145
      end

Simon Vocella's avatar
Simon Vocella committed
146
      it 'succeeds if it is an impersonation token' do
147
        impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
148

Simon Vocella's avatar
Simon Vocella committed
149
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
150
        expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_token, full_authentication_abilities))
151 152 153
      end

      it 'fails for personal access tokens with other scopes' do
Simon Vocella's avatar
Simon Vocella committed
154
        personal_access_token = create(:personal_access_token, scopes: ['read_user'])
155

Simon Vocella's avatar
Simon Vocella committed
156 157
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
        expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
158
      end
159

160 161 162 163 164 165
      it 'fails for impersonation token with other scopes' do
        impersonation_token = create(:personal_access_token, scopes: ['read_user'])

        expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
        expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
      end
166

Simon Vocella's avatar
Simon Vocella committed
167 168 169
      it 'fails if password is nil' do
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
        expect(gl_auth.find_for_git_client('', nil, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
170 171 172 173 174 175 176 177
      end
    end

    context 'while using regular user and password' do
      it 'falls through lfs authentication' do
        user = create(
          :user,
          username: 'normal_user',
178
          password: 'my-secret'
179 180 181 182 183 184 185 186 187 188
        )

        expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
          .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
      end

      it 'falls through oauth authentication when the username is oauth2' do
        user = create(
          :user,
          username: 'oauth2',
189
          password: 'my-secret'
190 191 192 193 194
        )

        expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
          .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
      end
195 196 197 198 199
    end

    it 'returns double nil for invalid credentials' do
      login = 'foo'

200 201
      expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login)
      expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new)
202 203 204
    end
  end

205
  describe 'find_with_user_password' do
206 207 208 209 210
    let!(:user) do
      create(:user,
        username: username,
        password: password,
        password_confirmation: password)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
211
    end
212
    let(:username) { 'John' }     # username isn't lowercase, test this
213
    let(:password) { 'my-secret' }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
214

215
    it "finds user by valid login/password" do
216
      expect( gl_auth.find_with_user_password(username, password) ).to eql user
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
217 218
    end

219
    it 'finds user by valid email/password with case-insensitive email' do
220
      expect(gl_auth.find_with_user_password(user.email.upcase, password)).to eql user
221 222
    end

223
    it 'finds user by valid username/password with case-insensitive username' do
224
      expect(gl_auth.find_with_user_password(username.upcase, password)).to eql user
225 226
    end

227
    it "does not find user with invalid password" do
228
      password = 'wrong'
229
      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
230 231
    end

232
    it "does not find user with invalid login" do
233
      user = 'wrong'
234
      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
235
    end
236

Pawel Chojnacki's avatar
Pawel Chojnacki committed
237 238
    include_examples 'user login operation with unique ip limit' do
      def operation
239
        expect(gl_auth.find_with_user_password(username, password)).to eq(user)
Pawel Chojnacki's avatar
Pawel Chojnacki committed
240 241 242
      end
    end

243 244 245 246 247 248 249 250 251 252 253 254
    it "does not find user in blocked state" do
      user.block

      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
    end

    it "does not find user in ldap_blocked state" do
      user.ldap_block

      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
    end

255
    context "with ldap enabled" do
256 257 258
      before do
        allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
      end
259 260

      it "tries to autheticate with db before ldap" do
261
        expect(Gitlab::LDAP::Authentication).not_to receive(:login)
262

263
        gl_auth.find_with_user_password(username, password)
264 265 266
      end

      it "uses ldap as fallback to for authentication" do
267
        expect(Gitlab::LDAP::Authentication).to receive(:login)
268

269
        gl_auth.find_with_user_password('ldap_user', 'password')
270 271
      end
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
272
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
273 274 275

  private

276
  def build_authentication_abilities
Kamil Trzcinski's avatar
Kamil Trzcinski committed
277 278 279 280 281 282 283 284
    [
      :read_project,
      :build_download_code,
      :build_read_container_image,
      :build_create_container_image
    ]
  end

285
  def read_authentication_abilities
Kamil Trzcinski's avatar
Kamil Trzcinski committed
286 287 288 289 290 291 292
    [
      :read_project,
      :download_code,
      :read_container_image
    ]
  end

293 294
  def full_authentication_abilities
    read_authentication_abilities + [
Kamil Trzcinski's avatar
Kamil Trzcinski committed
295
      :push_code,
Kamil Trzcinski's avatar
Kamil Trzcinski committed
296
      :create_container_image
Kamil Trzcinski's avatar
Kamil Trzcinski committed
297 298
    ]
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
299
end