auth_spec.rb 10.7 KB
Newer Older
D
Dmitriy Zaporozhets 已提交
1 2
require 'spec_helper'

D
Douwe Maan 已提交
3
describe Gitlab::Auth, lib: true do
4
  let(:gl_auth) { described_class }
D
Dmitriy Zaporozhets 已提交
5

J
Jacob Vosmaer 已提交
6
  describe 'find_for_git_client' do
K
Kamil Trzcinski 已提交
7 8 9 10 11 12 13 14 15 16 17 18
    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
19
          expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities))
K
Kamil Trzcinski 已提交
20 21 22 23 24
        end

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

25
          expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities))
K
Kamil Trzcinski 已提交
26 27 28
        end
      end

29
      (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
30 31 32 33 34 35 36 37 38
        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
39
            expect(subject).to eq(Gitlab::Auth::Result.new)
40
          end
K
Kamil Trzcinski 已提交
41 42 43 44 45
        end
      end
    end

    it 'recognizes other ci services' do
46
      project = create(:empty_project)
K
Kamil Trzcinski 已提交
47 48
      project.create_drone_ci_service(active: true)
      project.drone_ci_service.update(token: 'token')
F
Felipe Artur 已提交
49

50 51
      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))
52 53 54 55 56
    end

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

57 58
      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))
59 60
    end

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

    context 'unique ip limit is enabled and set to 1', :redis do
      before do
        allow(Gitlab::Auth::UniqueIpsLimiter).to receive_message_chain(:config, :unique_ips_limit_enabled).and_return(true)
        allow(Gitlab::Auth::UniqueIpsLimiter).to receive_message_chain(:config, :unique_ips_limit_time_window).and_return(10)
        allow(Gitlab::Auth::UniqueIpsLimiter).to receive_message_chain(:config, :unique_ips_limit_per_user).and_return(1)
      end

      it 'allows user authenticating from the same ip' do
        user = create(:user, password: 'password')
        allow(Gitlab::RequestContext).to receive(:client_ip).and_return('ip')
        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))
        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

      it 'blocks user authenticating from two distinct ips' do
        user = create(:user, password: 'password')
        allow(Gitlab::RequestContext).to receive(:client_ip).and_return('ip')
        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))
        allow(Gitlab::RequestContext).to receive(:client_ip).and_return('ip2')
        expect { gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip2') }.to raise_error(Gitlab::Auth::TooManyIps)
      end
    end

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

90 91 92
        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
93

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

98 99 100
        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
101

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

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

108 109 110 111 112 113 114 115 116 117
        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
118
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2')
119
        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, read_authentication_abilities))
120 121 122
      end

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

125 126
        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))
127
      end
128 129 130 131 132 133

      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
134 135
    end

136 137 138
    context 'while using personal access tokens as passwords' do
      let(:user) { create(:user) }
      let(:token_w_api_scope) { create(:personal_access_token, user: user, scopes: ['api']) }
139

140
      it 'succeeds for personal access tokens with the `api` scope' do
141
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.email)
142
        expect(gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities))
143 144 145 146
      end

      it 'fails for personal access tokens with other scopes' do
        personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
147

148 149
        expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: user.email)
        expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
150
      end
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180

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

        gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')
      end
    end

    context 'while using regular user and password' do
      it 'falls through lfs authentication' do
        user = create(
          :user,
          username: 'normal_user',
          password: 'my-secret',
        )

        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',
          password: 'my-secret',
        )

        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
181 182 183 184 185
    end

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

186 187
      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)
188 189 190
    end
  end

191
  describe 'find_with_user_password' do
192 193 194 195 196
    let!(:user) do
      create(:user,
        username: username,
        password: password,
        password_confirmation: password)
D
Dmitriy Zaporozhets 已提交
197
    end
198
    let(:username) { 'John' }     # username isn't lowercase, test this
199
    let(:password) { 'my-secret' }
D
Dmitriy Zaporozhets 已提交
200

201
    it "finds user by valid login/password" do
202
      expect( gl_auth.find_with_user_password(username, password) ).to eql user
D
Dmitriy Zaporozhets 已提交
203 204
    end

205
    it 'finds user by valid email/password with case-insensitive email' do
206
      expect(gl_auth.find_with_user_password(user.email.upcase, password)).to eql user
207 208
    end

209
    it 'finds user by valid username/password with case-insensitive username' do
210
      expect(gl_auth.find_with_user_password(username.upcase, password)).to eql user
211 212
    end

213
    it "does not find user with invalid password" do
214
      password = 'wrong'
215
      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
D
Dmitriy Zaporozhets 已提交
216 217
    end

218
    it "does not find user with invalid login" do
219
      user = 'wrong'
220
      expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
221
    end
222 223

    context "with ldap enabled" do
224 225 226
      before do
        allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
      end
227 228

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

231
        gl_auth.find_with_user_password(username, password)
232 233 234
      end

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

237
        gl_auth.find_with_user_password('ldap_user', 'password')
238 239
      end
    end
D
Dmitriy Zaporozhets 已提交
240
  end
K
Kamil Trzcinski 已提交
241 242 243

  private

244
  def build_authentication_abilities
K
Kamil Trzcinski 已提交
245 246 247 248 249 250 251 252
    [
      :read_project,
      :build_download_code,
      :build_read_container_image,
      :build_create_container_image
    ]
  end

253
  def read_authentication_abilities
K
Kamil Trzcinski 已提交
254 255 256 257 258 259 260
    [
      :read_project,
      :download_code,
      :read_container_image
    ]
  end

261 262
  def full_authentication_abilities
    read_authentication_abilities + [
K
Kamil Trzcinski 已提交
263
      :push_code,
K
Kamil Trzcinski 已提交
264
      :create_container_image
K
Kamil Trzcinski 已提交
265 266
    ]
  end
D
Dmitriy Zaporozhets 已提交
267
end