2022-01-01 13:38:12 +00:00
|
|
|
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
|
2021-06-01 12:20:20 +00:00
|
|
|
|
2017-04-19 10:09:54 +00:00
|
|
|
require 'rails_helper'
|
|
|
|
|
|
|
|
RSpec.describe Ldap do
|
|
|
|
|
2021-06-23 11:35:27 +00:00
|
|
|
describe 'initialization config parameters' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
# required as 'let' to perform test based
|
|
|
|
# expectations and reuse it in mock_initialization
|
|
|
|
# as return param of Net::LDAP.new
|
|
|
|
let(:mocked_ldap) { double(bind: true) }
|
|
|
|
|
|
|
|
def mock_initialization(given:, expected:)
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(Net::LDAP).to receive(:new).with(expected).and_return(mocked_ldap)
|
2017-04-19 10:09:54 +00:00
|
|
|
described_class.new(given)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'uses explicit host and port' do
|
|
|
|
|
|
|
|
config = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
}
|
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: config,
|
|
|
|
expected: config,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2021-06-23 11:35:27 +00:00
|
|
|
describe 'bind credentials' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
it 'uses given credentials' do
|
|
|
|
|
|
|
|
config = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
bind_user: 'JohnDoe',
|
|
|
|
bind_pw: 'zammad',
|
|
|
|
}
|
|
|
|
|
|
|
|
params = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
}
|
|
|
|
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:auth).with(config[:bind_user], config[:bind_pw])
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: config,
|
|
|
|
expected: params,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'requires bind_user' do
|
|
|
|
|
|
|
|
config = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
bind_pw: 'zammad',
|
|
|
|
}
|
|
|
|
|
|
|
|
params = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
}
|
|
|
|
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:auth)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: config,
|
|
|
|
expected: params,
|
|
|
|
)
|
2020-10-22 13:57:01 +00:00
|
|
|
expect(mocked_ldap).not_to have_received(:auth).with(config[:bind_user], config[:bind_pw])
|
2017-04-19 10:09:54 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'requires bind_pw' do
|
|
|
|
|
|
|
|
config = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
bind_user: 'JohnDoe',
|
|
|
|
}
|
|
|
|
|
|
|
|
params = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
}
|
|
|
|
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:auth)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: config,
|
|
|
|
expected: params,
|
|
|
|
)
|
2020-10-22 13:57:01 +00:00
|
|
|
expect(mocked_ldap).not_to have_received(:auth).with(config[:bind_user], config[:bind_pw])
|
2017-04-19 10:09:54 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'extracts port from host' do
|
|
|
|
|
|
|
|
config = {
|
|
|
|
host: 'localhost:1337'
|
|
|
|
}
|
|
|
|
|
|
|
|
params = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
}
|
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: config,
|
|
|
|
expected: params,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2021-06-23 11:35:27 +00:00
|
|
|
describe 'host_url' do
|
2017-04-19 10:09:54 +00:00
|
|
|
it 'parses protocol and host' do
|
|
|
|
config = {
|
|
|
|
host_url: 'ldaps://localhost'
|
|
|
|
}
|
|
|
|
|
|
|
|
params = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 636,
|
|
|
|
encryption: Hash
|
|
|
|
}
|
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: config,
|
|
|
|
expected: params,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'prefers parsing over explicit parameters' do
|
|
|
|
config = {
|
|
|
|
host: 'anotherhost',
|
|
|
|
port: 7777,
|
|
|
|
host_url: 'ldap://localhost:389'
|
|
|
|
}
|
|
|
|
|
|
|
|
params = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 389,
|
|
|
|
}
|
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: config,
|
|
|
|
expected: params,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'falls back to default ldap port' do
|
|
|
|
config = {
|
|
|
|
host: 'localhost',
|
|
|
|
}
|
|
|
|
|
|
|
|
params = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 389,
|
|
|
|
}
|
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: config,
|
|
|
|
expected: params,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'uses explicit ssl' do
|
|
|
|
|
|
|
|
config = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
ssl: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
encryption: Hash,
|
|
|
|
}
|
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: config,
|
|
|
|
expected: expected,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "uses 'ldap_config' Setting as fallback" do
|
|
|
|
|
|
|
|
config = {
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
}
|
|
|
|
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(Setting).to receive(:get)
|
|
|
|
allow(Setting).to receive(:get).with('ldap_config').and_return(config)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
mock_initialization(
|
|
|
|
given: nil,
|
|
|
|
expected: config,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-06-23 11:35:27 +00:00
|
|
|
describe 'instance methods' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
# required as 'let' to perform test based
|
|
|
|
# expectations and reuse it in 'let' instance
|
|
|
|
# as return param of Net::LDAP.new
|
|
|
|
let(:mocked_ldap) { double(bind: true) }
|
2017-10-01 12:31:29 +00:00
|
|
|
let(:instance) do
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(Net::LDAP).to receive(:new).and_return(mocked_ldap)
|
2017-04-19 10:09:54 +00:00
|
|
|
described_class.new(
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
)
|
2017-10-01 12:31:29 +00:00
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2020-02-18 19:51:31 +00:00
|
|
|
describe '#preferences' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
it 'responds to #preferences' do
|
|
|
|
expect(instance).to respond_to(:preferences)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns preferences' do
|
|
|
|
attributes = {
|
|
|
|
namingcontexts: ['ou=dep1,ou=org', 'ou=dep2,ou=org']
|
|
|
|
}
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:search_root_dse).and_return(attributes)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
expect(instance.preferences).to eq(attributes)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-18 19:51:31 +00:00
|
|
|
describe '#search' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2019-04-15 01:41:17 +00:00
|
|
|
let(:base) { 'DC=domain,DC=tld' }
|
|
|
|
let(:filter) { '(objectClass=user)' }
|
|
|
|
|
2017-04-19 10:09:54 +00:00
|
|
|
it 'responds to #search' do
|
|
|
|
expect(instance).to respond_to(:search)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'performs search for a filter, base and scope and yields of returned entries' do
|
|
|
|
|
|
|
|
scope = Net::LDAP::SearchScope_BaseObject
|
|
|
|
|
|
|
|
additional = {
|
2018-12-19 17:31:51 +00:00
|
|
|
base: base,
|
|
|
|
scope: scope,
|
2017-04-19 10:09:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
filter: filter,
|
|
|
|
base: base,
|
|
|
|
scope: scope,
|
|
|
|
}
|
|
|
|
|
|
|
|
yield_entry = build(:ldap_entry)
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:search).with(include(expected)).and_yield(yield_entry).and_return(true)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
check_entry = nil
|
2021-07-06 07:52:22 +00:00
|
|
|
instance.search(filter, **additional) { |entry| check_entry = entry }
|
2017-04-19 10:09:54 +00:00
|
|
|
expect(check_entry).to eq(yield_entry)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'falls back to whole subtree scope search' do
|
|
|
|
|
|
|
|
additional = {
|
|
|
|
base: base,
|
|
|
|
}
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
filter: filter,
|
|
|
|
base: base,
|
|
|
|
scope: Net::LDAP::SearchScope_WholeSubtree,
|
|
|
|
}
|
|
|
|
|
|
|
|
yield_entry = build(:ldap_entry)
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:search).with(include(expected)).and_yield(yield_entry).and_return(true)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
check_entry = nil
|
2021-07-06 07:52:22 +00:00
|
|
|
instance.search(filter, **additional) { |entry| check_entry = entry }
|
2017-04-19 10:09:54 +00:00
|
|
|
expect(check_entry).to eq(yield_entry)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'falls back to base_dn configuration parameter' do
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
filter: filter,
|
|
|
|
base: base,
|
|
|
|
scope: Net::LDAP::SearchScope_WholeSubtree,
|
|
|
|
}
|
|
|
|
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(Net::LDAP).to receive(:new).and_return(mocked_ldap)
|
2017-04-19 10:09:54 +00:00
|
|
|
instance = described_class.new(
|
|
|
|
host: 'localhost',
|
|
|
|
port: 1337,
|
|
|
|
base_dn: base,
|
|
|
|
)
|
|
|
|
|
|
|
|
yield_entry = build(:ldap_entry)
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:search).with(include(expected)).and_yield(yield_entry).and_return(true)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
check_entry = nil
|
|
|
|
instance.search(filter) { |entry| check_entry = entry }
|
|
|
|
expect(check_entry).to eq(yield_entry)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-18 19:51:31 +00:00
|
|
|
describe '#entries?' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2019-04-15 01:41:17 +00:00
|
|
|
let(:filter) { '(objectClass=user)' }
|
|
|
|
|
2017-04-19 10:09:54 +00:00
|
|
|
it 'responds to #entries?' do
|
|
|
|
expect(instance).to respond_to(:entries?)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns true if entries are present' do
|
|
|
|
params = {
|
|
|
|
filter: filter
|
|
|
|
}
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:search).with(include(params)).and_yield(build(:ldap_entry)).and_return(nil)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
expect(instance.entries?(filter)).to be true
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false if no entries are present' do
|
|
|
|
params = {
|
|
|
|
filter: filter
|
|
|
|
}
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:search).with(include(params)).and_return(true)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
expect(instance.entries?(filter)).to be false
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|