access_spec.rb 14.7 KB
Newer Older
1 2
# frozen_string_literal: true

3 4
require 'spec_helper'

5
RSpec.describe Gitlab::Auth::Ldap::Access do
6 7 8
  include LdapHelpers

  let(:user) { create(:omniauth_user) }
9
  let(:provider) { user.ldap_identity.provider }
10

11
  subject(:access) { described_class.new(user) }
12 13

  describe '#allowed?' do
14 15
    context 'LDAP user' do
      it 'finds a user by dn first' do
16 17
        expect(Gitlab::Auth::Ldap::Person).to receive(:find_by_dn).and_return(:ldap_user)
        expect(Gitlab::Auth::Ldap::Person).not_to receive(:find_by_email)
18

19
        access.allowed?
20 21
      end

22
      it 'finds a user by email if not found by dn' do
23 24
        expect(Gitlab::Auth::Ldap::Person).to receive(:find_by_dn).and_return(nil)
        expect(Gitlab::Auth::Ldap::Person).to receive(:find_by_email)
25

26 27 28 29 30 31 32 33
        access.allowed?
      end

      it 'returns false if user cannot be found' do
        stub_ldap_person_find_by_dn(nil)
        stub_ldap_person_find_by_email(user.email, nil)

        expect(access.allowed?).to be_falsey
34
      end
35 36 37

      context 'when exists in LDAP/AD' do
        before do
38
          allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_dn).and_return(user)
39 40 41 42
        end

        context 'user blocked in LDAP/AD' do
          before do
43
            allow(Gitlab::Auth::Ldap::Person).to receive(:disabled_via_active_directory?).and_return(true)
44 45 46 47 48 49 50 51 52 53
          end

          it 'blocks user in GitLab' do
            expect(access.allowed?).to be_falsey
            expect(user.blocked?).to be_truthy
            expect(user.ldap_blocked?).to be_truthy
          end

          context 'on a read-only instance' do
            before do
54
              allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
55 56 57 58 59 60 61 62 63 64 65 66 67
            end

            it 'does not block user in GitLab' do
              expect(access.allowed?).to be_falsey
              expect(user.blocked?).to be_falsey
              expect(user.ldap_blocked?).to be_falsey
            end
          end
        end

        context 'user unblocked in LDAP/AD' do
          before do
            user.update_column(:state, :ldap_blocked)
68
            allow(Gitlab::Auth::Ldap::Person).to receive(:disabled_via_active_directory?).and_return(false)
69 70 71 72 73 74 75 76 77 78
          end

          it 'unblocks user in GitLab' do
            expect(access.allowed?).to be_truthy
            expect(user.blocked?).to be_falsey
            expect(user.ldap_blocked?).to be_falsey
          end

          context 'on a read-only instance' do
            before do
79
              allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
            end

            it 'does not unblock user in GitLab' do
              expect(access.allowed?).to be_truthy
              expect(user.blocked?).to be_truthy
              expect(user.ldap_blocked?).to be_truthy
            end
          end
        end
      end

      context 'when no longer exist in LDAP/AD' do
        before do
          stub_ldap_person_find_by_dn(nil)
          stub_ldap_person_find_by_email(user.email, nil)
        end

        it 'blocks user in GitLab' do
          expect(access.allowed?).to be_falsey
          expect(user.blocked?).to be_truthy
          expect(user.ldap_blocked?).to be_truthy
        end

        context 'on a read-only instance' do
          before do
105
            allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
106 107 108 109 110 111 112 113 114
          end

          it 'does not block user in GitLab' do
            expect(access.allowed?).to be_falsey
            expect(user.blocked?).to be_falsey
            expect(user.ldap_blocked?).to be_falsey
          end
        end
      end
115 116 117 118
    end
  end

  describe '#update_user' do
119
    let(:entry) { Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com") }
120

121 122 123 124
    context 'email address' do
      before do
        stub_ldap_person_find_by_dn(entry, provider)
      end
125

126 127 128
      it 'does not update email if email attribute is not set' do
        expect { access.update_user }.not_to change(user, :email)
      end
129

130 131
      it 'does not update the email if the user has the same email in GitLab and in LDAP' do
        entry['mail'] = [user.email]
132

133 134
        expect { access.update_user }.not_to change(user, :email)
      end
135

136 137
      it 'does not update the email if the user has the same email GitLab and in LDAP, but with upper case in LDAP' do
        entry['mail'] = [user.email.upcase]
138

139 140
        expect { access.update_user }.not_to change(user, :email)
      end
141

icode's avatar
icode committed
142 143 144 145
      context 'when mail is different' do
        before do
          entry['mail'] = ['new_email@example.com']
        end
146

icode's avatar
icode committed
147
        it 'does not update the email when in a read-only GitLab instance' do
148
          allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
149

icode's avatar
icode committed
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
          expect { access.update_user }.not_to change(user, :email)
        end

        it 'updates the email if the user email is different' do
          expect { access.update_user }.to change(user, :email)
        end

        it 'does not update the name if the user email is different' do
          expect { access.update_user }.not_to change(user, :name)
        end
      end
    end

    context 'name' do
      before do
        stub_ldap_person_find_by_dn(entry, provider)
166
      end
167

icode's avatar
icode committed
168 169 170 171 172 173 174 175 176 177 178 179 180 181
      it 'does not update name if name attribute is not set' do
        expect { access.update_user }.not_to change(user, :name)
      end

      it 'does not update the name if the user has the same name in GitLab and in LDAP' do
        entry['cn'] = [user.name]

        expect { access.update_user }.not_to change(user, :name)
      end

      context 'when cn is different' do
        before do
          entry['cn'] = ['New Name']
        end
182

icode's avatar
icode committed
183
        it 'does not update the name when in a read-only GitLab instance' do
184
          allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
icode's avatar
icode committed
185 186 187 188 189 190 191 192 193

          expect { access.update_user }.not_to change(user, :name)
        end

        it 'updates the name if the user name is different' do
          expect { access.update_user }.to change(user, :name)
        end

        it 'does not update the email if the user name is different' do
194 195 196 197 198 199 200 201 202 203 204
          expect { access.update_user }.not_to change(user, :email)
        end
      end

      context 'when first and last name attributes passed' do
        before do
          entry['givenName'] = ['Jane']
          entry['sn'] = ['Doe']
        end

        it 'does not update the name when in a read-only GitLab instance' do
205
          allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
206 207 208 209 210 211 212 213 214

          expect { access.update_user }.not_to change(user, :name)
        end

        it 'updates the name if the user name is different' do
          expect { access.update_user }.to change(user, :name).to('Jane Doe')
        end

        it 'does not update the email if the user name is different' do
icode's avatar
icode committed
215 216
          expect { access.update_user }.not_to change(user, :email)
        end
217
      end
218 219
    end

220 221 222 223 224 225 226
    context 'group memberships' do
      context 'when there is `memberof` param' do
        before do
          entry['memberof'] = [
            'CN=Group1,CN=Users,DC=The dc,DC=com',
            'CN=Group2,CN=Builtin,DC=The dc,DC=com'
          ]
227

228 229
          stub_ldap_person_find_by_dn(entry, provider)
        end
230

231
        it 'triggers a sync for all groups found in `memberof` for new users' do
232 233 234
          group_link_1 = create(:ldap_group_link, cn: 'Group1', provider: provider)
          group_link_2 = create(:ldap_group_link, cn: 'Group2', provider: provider)
          group_ids = [group_link_1, group_link_2].map(&:group_id)
235

236 237
          expect(LdapGroupSyncWorker).to receive(:perform_async)
            .with(a_collection_containing_exactly(*group_ids), provider)
238

239
          access.update_user
240
        end
241

242
        it "doesn't trigger a sync when in a read-only GitLab instance" do
243
          allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
244 245
          create(:ldap_group_link, cn: 'Group1', provider: provider)
          create(:ldap_group_link, cn: 'Group2', provider: provider)
246

247
          expect(LdapGroupSyncWorker).not_to receive(:perform_async)
248

249
          access.update_user
250
        end
251

252 253 254 255
        it "doesn't trigger a sync when there are no links for the provider" do
          _another_provider = create(:ldap_group_link,
                                     cn: 'Group1',
                                     provider: 'not-this-ldap')
256

257
          expect(LdapGroupSyncWorker).not_to receive(:perform_async)
258

259
          access.update_user
260 261 262 263 264 265 266 267
        end

        it 'does not performs the membership update for existing users' do
          user.created_at = Time.now - 10.minutes
          user.last_credential_check_at = Time.now
          user.save

          expect(LdapGroupLink).not_to receive(:where)
268
          expect(LdapGroupSyncWorker).not_to receive(:perform_async)
269

270 271 272
          access.update_user
        end
      end
273

274 275
      it "doesn't continue when there is no `memberOf` param" do
        stub_ldap_person_find_by_dn(entry, provider)
276

277
        expect(LdapGroupLink).not_to receive(:where)
278
        expect(LdapGroupSyncWorker).not_to receive(:perform_async)
279

280 281
        access.update_user
      end
282 283
    end

284 285 286 287
    context 'SSH keys' do
      let(:ssh_key) { 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrSQHff6a1rMqBdHFt+FwIbytMZ+hJKN3KLkTtOWtSvNIriGhnTdn4rs+tjD/w+z+revytyWnMDM9dS7J8vQi006B16+hc9Xf82crqRoPRDnBytgAFFQY1G/55ql2zdfsC5yvpDOFzuwIJq5dNGsojS82t6HNmmKPq130fzsenFnj5v1pl3OJvk513oduUyKiZBGTroWTn7H/eOPtu7s9MD7pAdEjqYKFLeaKmyidiLmLqQlCRj3Tl2U9oyFg4PYNc0bL5FZJ/Z6t0Ds3i/a2RanQiKxrvgu3GSnUKMx7WIX373baL4jeM7cprRGiOY/1NcS+1cAjfJ8oaxQF/1dYj' }
      let(:ssh_key_attribute_name) { 'altSecurityIdentities' }
      let(:entry) { Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com\n#{ssh_key_attribute_name}: SSHKey:#{ssh_key}\n#{ssh_key_attribute_name}: KerberosKey:bogus") }
288 289

      before do
290
        stub_ldap_config(sync_ssh_keys: ssh_key_attribute_name, sync_ssh_keys?: true)
291 292
      end

293 294
      it 'adds a SSH key if it is in LDAP but not in gitlab' do
        stub_ldap_person_find_by_dn(entry, provider)
295

296
        expect { access.update_user }.to change(user.keys, :count).from(0).to(1)
297 298
      end

299 300 301 302
      it 'adds a SSH key and give it a proper name' do
        stub_ldap_person_find_by_dn(entry, provider)

        access.update_user
303

304 305
        expect(user.keys.last.title).to match(/LDAP/)
        expect(user.keys.last.title).to match(/#{ssh_key_attribute_name}/)
306 307
      end

308 309 310
      it 'does not add a SSH key if it is invalid' do
        entry = Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com\n#{ssh_key_attribute_name}: I am not a valid key")
        stub_ldap_person_find_by_dn(entry, provider)
311

312 313
        expect { access.update_user }.not_to change(user.keys, :count)
      end
314

315
      it 'does not add a SSH key when in a read-only GitLab instance' do
316
        allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
317
        stub_ldap_person_find_by_dn(entry, provider)
318

319 320
        expect { access.update_user }.not_to change(user.keys, :count)
      end
321

322 323 324 325
      context 'user has at least one LDAPKey' do
        before do
          user.keys.ldap.create key: ssh_key, title: 'to be removed'
        end
326

327 328 329
        it 'removes a SSH key if it is no longer in LDAP' do
          entry = Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com\n#{ssh_key_attribute_name}:\n")
          stub_ldap_person_find_by_dn(entry, provider)
330

331 332
          expect { access.update_user }.to change(user.keys, :count).from(1).to(0)
        end
333

334 335 336 337 338 339 340
        it 'removes a SSH key if the ldap attribute was removed' do
          entry = Net::LDAP::Entry.from_single_ldif_string("dn: cn=foo, dc=bar, dc=com")
          stub_ldap_person_find_by_dn(entry, provider)

          expect { access.update_user }.to change(user.keys, :count).from(1).to(0)
        end
      end
341 342
    end

343 344 345 346 347 348
    context 'kerberos identity' do
      before do
        stub_ldap_config(active_directory: true)
        stub_kerberos_setting(enabled: true)
        stub_ldap_person_find_by_dn(entry, provider)
      end
349

350
      it 'adds a Kerberos identity if it is in Active Directory but not in GitLab' do
351
        allow_any_instance_of(EE::Gitlab::Auth::Ldap::Person).to receive_messages(kerberos_principal: 'mylogin@FOO.COM')
352

353 354 355
        expect { access.update_user }.to change(user.identities.where(provider: :kerberos), :count).from(0).to(1)
        expect(user.identities.where(provider: 'kerberos').last.extern_uid).to eq('mylogin@FOO.COM')
      end
356

357
      it 'updates existing Kerberos identity in GitLab if Active Directory has a different one' do
358
        allow_any_instance_of(EE::Gitlab::Auth::Ldap::Person).to receive_messages(kerberos_principal: 'otherlogin@BAR.COM')
359
        user.identities.build(provider: 'kerberos', extern_uid: 'mylogin@FOO.COM').save
360

361 362 363
        expect { access.update_user }.not_to change(user.identities.where(provider: 'kerberos'), :count)
        expect(user.identities.where(provider: 'kerberos').last.extern_uid).to eq('otherlogin@BAR.COM')
      end
364

365
      it 'does not remove Kerberos identities from GitLab if they are none in the LDAP provider' do
366
        allow_any_instance_of(EE::Gitlab::Auth::Ldap::Person).to receive_messages(kerberos_principal: nil)
367
        user.identities.build(provider: 'kerberos', extern_uid: 'otherlogin@BAR.COM').save
368

369 370 371
        expect { access.update_user }.not_to change(user.identities.where(provider: 'kerberos'), :count)
        expect(user.identities.where(provider: 'kerberos').last.extern_uid).to eq('otherlogin@BAR.COM')
      end
372

373
      it 'does not modify identities in GitLab if they are no kerberos principal in the LDAP provider' do
374
        allow_any_instance_of(EE::Gitlab::Auth::Ldap::Person).to receive_messages(kerberos_principal: nil)
375

376 377
        expect { access.update_user }.not_to change(user.identities, :count)
      end
378

379
      it 'does not add a Kerberos identity when in a read-only GitLab instance' do
380
        allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
381
        allow_any_instance_of(EE::Gitlab::Auth::Ldap::Person).to receive_messages(kerberos_principal: 'mylogin@FOO.COM')
382

383 384
        expect { access.update_user }.not_to change(user.identities.where(provider: :kerberos), :count)
      end
385 386
    end

387 388 389 390 391
    context 'LDAP entity' do
      context 'whent external UID changed in the entry' do
        before do
          stub_ldap_person_find_by_dn(ldap_user_entry('another uid'), provider)
        end
392

393 394
        it 'updates the external UID' do
          access.update_user
395

396 397 398
          expect(user.ldap_identity.reload.extern_uid)
            .to eq('uid=another uid,ou=users,dc=example,dc=com')
        end
399

400
        it 'does not update the external UID when in a read-only GitLab instance' do
401
          allow(Gitlab::Database.main).to receive(:read_only?).and_return(true)
402 403 404 405 406 407

          access.update_user

          expect(user.ldap_identity.reload.extern_uid).to eq('123456')
        end
      end
408 409 410
    end
  end
end