require 'spec_helper'

describe Gitlab::LDAP::Access do
  let(:access) { Gitlab::LDAP::Access.new user }
  let(:user) { create(:omniauth_user) }

  describe :allowed? do
    subject { access.allowed? }

    context 'when the user cannot be found' do
      before { Gitlab::LDAP::Person.stub(find_by_dn: nil) }

      it { is_expected.to be_falsey }
    end

    context 'when the user is found' do
      before { Gitlab::LDAP::Person.stub(find_by_dn: :ldap_user) }

      context 'and the user is disabled via active directory' do
        before { Gitlab::LDAP::Person.stub(disabled_via_active_directory?: true) }

        it { is_expected.to be_falsey }

        it "should block user in GitLab" do
          access.allowed?
          user.should be_blocked
        end
      end

      context 'and has no disabled flag in active diretory' do
        before do
          user.block

          Gitlab::LDAP::Person.stub(disabled_via_active_directory?: false)
        end

        it { is_expected.to be_truthy }

        context 'when auto-created users are blocked' do

          before do
            Gitlab::LDAP::Config.any_instance.stub(block_auto_created_users: true)
          end

          it "does not unblock user in GitLab" do
            access.allowed?
            user.should be_blocked
          end
        end

        context "when auto-created users are not blocked" do

          before do
            Gitlab::LDAP::Config.any_instance.stub(block_auto_created_users: false)
          end

          it "should unblock user in GitLab" do
            access.allowed?
            user.should_not be_blocked
          end
        end
      end

      context 'withoud ActiveDirectory enabled' do
        before do
          Gitlab::LDAP::Config.stub(enabled?: true)
          Gitlab::LDAP::Config.any_instance.stub(active_directory: false)
        end

        it { is_expected.to be_truthy }
      end
    end
  end

  describe :update_permissions do
    subject { access.update_permissions }

    it "syncs ssh keys if enabled by configuration" do
      access.stub sync_ssh_keys?: 'sshpublickey'
      expect(access).to receive(:update_ssh_keys).once

      subject
    end

    it "does update group permissions with a group base configured" do
      access.stub group_base: 'my-group-base'
      expect(access).to receive(:update_ldap_group_links)

      subject
    end

    it "does not update group permissions without a group base configured" do
      access.stub group_base: ''
      expect(access).not_to receive(:update_ldap_group_links)

      subject
    end

    it "does update admin group permissions if admin group is configured" do
      access.stub admin_group: 'my-admin-group'
      access.stub :update_ldap_group_links
      expect(access).to receive(:update_admin_status)

      subject
    end
  end

  describe :update_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") }

    before do
      Gitlab::LDAP::Config.any_instance.stub(sync_ssh_keys: ssh_key_attribute_name)
      access.stub sync_ssh_keys?: true
    end

    it "should add a SSH key if it is in LDAP but not in gitlab" do
      Gitlab::LDAP::Adapter.any_instance.stub(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') }

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

    it "should add a SSH key and give it a proper name" do
      Gitlab::LDAP::Adapter.any_instance.stub(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') }

      access.update_ssh_keys
      expect(user.keys.last.title).to match(/LDAP/)
      expect(user.keys.last.title).to match(/#{access.ldap_config.sync_ssh_keys}/)
    end

    it "should 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")
      Gitlab::LDAP::Adapter.any_instance.stub(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') }

      expect{ access.update_ssh_keys }.to_not change(user.keys, :count)
    end

    context 'user has at least one LDAPKey' do
      before { user.keys.ldap.create key: ssh_key, title: 'to be removed' }

      it "should remove 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")
        Gitlab::LDAP::Adapter.any_instance.stub(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') }

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

      it "should remove 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")
        Gitlab::LDAP::Adapter.any_instance.stub(:user) { Gitlab::LDAP::Person.new(entry, 'ldapmain') }

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

  describe :update_user_email do
    let(:entry) { Net::LDAP::Entry.new }

    before do
      access.stub ldap_user: Gitlab::LDAP::Person.new(entry, user.ldap_identity.provider)
    end

    it "should not update email if email attribute is not set" do
      expect{ access.update_email }.to_not change(user, :email)
    end

    it "should not update the email if the user has the same email in GitLab and in LDAP" do
      entry['mail'] = [user.email]
      expect{ access.update_email }.to_not change(user, :email)
    end

    it "should 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]
      expect{ access.update_email }.to_not change(user, :email)
    end

    it "should update the email if the user email is different" do
      entry['mail'] = ["new_email@example.com"]
      expect{ access.update_email }.to change(user, :email)
    end
  end


  describe :update_admin_status do
    before do
      access.stub(admin_group: "GLAdmins")
      ldap_user_entry = Net::LDAP::Entry.new
      Gitlab::LDAP::Adapter.any_instance.stub(:user) { Gitlab::LDAP::Person.new(ldap_user_entry, user.ldap_identity.provider) }
      Gitlab::LDAP::Person.any_instance.stub(:uid) { 'admin2' }
    end

    it "should give admin privileges to an User" do
      admin_group = Net::LDAP::Entry.from_single_ldif_string(
%Q{dn: cn=#{access.admin_group},ou=groups,dc=bar,dc=com
cn: #{access.admin_group}
description: GitLab admins
gidnumber: 42
memberuid: admin1
memberuid: admin2
memberuid: admin3
objectclass: top
objectclass: posixGroup
})
      Gitlab::LDAP::Adapter.any_instance.stub(:group) { Gitlab::LDAP::Group.new(admin_group) }

      expect{ access.update_admin_status }.to change(user, :admin?).to(true)
    end

    it "should remove admin privileges from an User" do
      user.update_attribute(:admin, true)
      admin_group = Net::LDAP::Entry.from_single_ldif_string(
%Q{dn: cn=#{access.admin_group},ou=groups,dc=bar,dc=com
cn: #{access.admin_group}
description: GitLab admins
gidnumber: 42
memberuid: admin1
memberuid: admin3
objectclass: top
objectclass: posixGroup
})
      Gitlab::LDAP::Adapter.any_instance.stub(:group) { Gitlab::LDAP::Group.new(admin_group) }
      expect{ access.update_admin_status }.to change(user, :admin?).to(false)
    end
  end


  describe :update_ldap_group_links do
    let(:cns_with_access) { %w(ldap-group1 ldap-group2) }
    let(:gitlab_group_1) { create :group }
    let(:gitlab_group_2) { create :group }

    before do
      access.stub(cns_with_access: cns_with_access)
    end

    context "non existing access for group-1, allowed via ldap-group1 as MASTER" do
      before do
        gitlab_group_1.ldap_group_links.create({
          cn: 'ldap-group1', group_access: Gitlab::Access::MASTER, provider: 'ldapmain' })
      end

      it "gives the user master access for group 1" do
        access.update_ldap_group_links
        expect( gitlab_group_1.has_master?(user) ).to be_truthy
      end
    end

    context "existing access as guest for group-1, allowed via ldap-group1 as DEVELOPER" do
      before do
        gitlab_group_1.group_members.guests.create(user_id: user.id)
        gitlab_group_1.ldap_group_links.create({
          cn: 'ldap-group1', group_access: Gitlab::Access::MASTER, provider: 'ldapmain' })
      end

      it "upgrades the users access to master for group 1" do
        expect { access.update_ldap_group_links }.to \
          change{ gitlab_group_1.has_master?(user) }.from(false).to(true)
      end
    end

    context "existing access as MASTER for group-1, allowed via ldap-group1 as DEVELOPER" do
      before do
        gitlab_group_1.group_members.masters.create(user_id: user.id)
        gitlab_group_1.ldap_group_links.create({
          cn: 'ldap-group1', group_access: Gitlab::Access::DEVELOPER, provider: 'ldapmain' })
      end

      it "keeps the users master access for group 1" do
        expect { access.update_ldap_group_links }.not_to \
          change{ gitlab_group_1.has_master?(user) }
      end
    end

    context "existing access as master for group-1, not allowed" do
      before do
        gitlab_group_1.group_members.masters.create(user_id: user.id)
        gitlab_group_1.ldap_group_links.create({
          cn: 'ldap-group1', group_access: Gitlab::Access::MASTER, provider: 'ldapmain'})
        access.stub(cns_with_access: ['ldap-group2'])
      end

      it "removes user from gitlab_group_1" do
        expect { access.update_ldap_group_links }.to \
          change{ gitlab_group_1.members.where(user_id: user).any? }.from(true).to(false)
      end
    end

    context "existing access as owner for group-1 with no other owner, not allowed" do
      before do
        gitlab_group_1.group_members.owners.create(user_id: user.id)
        gitlab_group_1.ldap_group_links.create({
          cn: 'ldap-group1', group_access: Gitlab::Access::OWNER, provider: 'ldapmain'})
        access.stub(cns_with_access: ['ldap-group2'])
      end

      it "does not remove the user from gitlab_group_1 since it's the last owner" do
        expect { access.update_ldap_group_links }.not_to \
          change{ gitlab_group_1.has_owner?(user) }
      end
    end

    context "existing access as owner for group-1 while other owners present, not allowed" do
      before do
        owner2 = create(:user) # a 2nd owner
        gitlab_group_1.group_members.owners.create([ {user_id: user.id}, {user_id: owner2.id} ] )
        gitlab_group_1.ldap_group_links.create({
          cn: 'ldap-group1', group_access: Gitlab::Access::OWNER, provider: 'ldapmain'})
        access.stub(cns_with_access: ['ldap-group2'])
      end

      it "removes user from gitlab_group_1" do
        expect { access.update_ldap_group_links }.to \
          change{ gitlab_group_1.members.where(user_id: user).any? }.from(true).to(false)
      end
    end
  end

  describe 'ldap_groups' do
    let(:ldap_group_1) do
      Net::LDAP::Entry.from_single_ldif_string(
%Q{dn: cn=#{access.ldap_config.admin_group},ou=groups,dc=bar,dc=com
cn: #{access.ldap_config.admin_group}
description: GitLab group 1
gidnumber: 42
memberuid: user1
memberuid: user2
objectclass: top
objectclass: posixGroup
})
    end

    it "returns an interator of LDAP Groups" do
      ::LdapGroupLink.create({
        cn: 'example', group_access: Gitlab::Access::DEVELOPER, group_id: 42, provider: 'ldapmain' })
      Gitlab::LDAP::Adapter.any_instance.stub(:group) { Gitlab::LDAP::Group.new(ldap_group_1) }

      expect(access.ldap_groups.first).to be_a Gitlab::LDAP::Group
    end

    it "only returns found ldap groups" do
      ::LdapGroupLink.create cn: 'example', group_access: Gitlab::Access::DEVELOPER, group_id: 42
      Gitlab::LDAP::Group.stub(find_by_cn: nil) # group not found

      expect(access.ldap_groups).to be_empty
    end
  end

  describe :cns_with_access do
    let(:ldap_group_response_1) do
      Net::LDAP::Entry.from_single_ldif_string(
%Q{dn: cn=group1,ou=groups,dc=bar,dc=com
cn: group1
description: GitLab group 1
gidnumber: 21
uniquemember: #{ldap_user.dn.downcase}
uniquemember: uid=user2,ou=people,dc=example
objectclass: top
objectclass: posixGroup
})
    end
    let(:ldap_group_response_2) do
      Net::LDAP::Entry.from_single_ldif_string(
%Q{dn: cn=group2,ou=groups,dc=bar,dc=com
cn: group2
description: GitLab group 2
gidnumber: 42
memberuid: user3
memberuid: user4
objectclass: top
objectclass: posixGroup
})
    end
    let(:ldap_groups) do
      [
        Gitlab::LDAP::Group.new(ldap_group_response_1),
        Gitlab::LDAP::Group.new(ldap_group_response_2)
      ]
    end
    let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, user.ldap_identity.provider) }

    before do
      access.stub(ldap_user: ldap_user)
      ldap_user.stub(:uid) { 'user1' }
      ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' }
    end

    it "only returns ldap cns to which the user has access" do
      access.stub(ldap_groups: ldap_groups)
      expect(access.cns_with_access).to eql ['group1']
    end
  end
end
