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'
|
|
|
|
# rails autoloading issue
|
|
|
|
require 'ldap'
|
|
|
|
require 'ldap/user'
|
2018-08-31 15:27:57 +00:00
|
|
|
require 'tcr/net/ldap'
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
RSpec.describe Ldap::User do
|
|
|
|
|
2021-07-16 13:29:38 +00:00
|
|
|
let(:mocked_ldap) { double }
|
2019-04-15 01:41:17 +00:00
|
|
|
|
2020-02-18 19:51:31 +00:00
|
|
|
describe '.uid_attribute' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
it 'responds to .uid_attribute' do
|
|
|
|
expect(described_class).to respond_to(:uid_attribute)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns uid attribute string from given attribute strucutre' do
|
|
|
|
attributes = {
|
2018-01-22 15:53:53 +00:00
|
|
|
objectguid: 'TEST',
|
|
|
|
custom: 'value',
|
2017-04-19 10:09:54 +00:00
|
|
|
}
|
2018-01-22 15:53:53 +00:00
|
|
|
expect(described_class.uid_attribute(attributes)).to eq('objectguid')
|
2017-04-19 10:09:54 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil if no attribute could be found' do
|
|
|
|
attributes = {
|
|
|
|
custom: 'value',
|
|
|
|
}
|
|
|
|
expect(described_class.uid_attribute(attributes)).to be nil
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
# required as 'let' to perform test based
|
|
|
|
# expectations and reuse it in 'let' instance
|
|
|
|
# as additional parameter
|
|
|
|
|
2021-06-23 11:35:27 +00:00
|
|
|
describe 'initialization config parameters' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
it 'reuses given Ldap instance if given' do
|
|
|
|
expect(Ldap).not_to receive(:new)
|
2019-04-15 01:41:17 +00:00
|
|
|
described_class.new(ldap: mocked_ldap)
|
2017-04-19 10:09:54 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'takes optional filter' do
|
|
|
|
|
|
|
|
filter = '(objectClass=custom)'
|
|
|
|
config = {
|
|
|
|
filter: filter
|
|
|
|
}
|
|
|
|
|
|
|
|
instance = described_class.new(config, ldap: mocked_ldap)
|
|
|
|
|
|
|
|
expect(instance.filter).to eq(filter)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'takes optional uid_attribute' do
|
|
|
|
|
2018-01-22 15:53:53 +00:00
|
|
|
uid_attribute = 'objectguid'
|
2017-04-19 10:09:54 +00:00
|
|
|
config = {
|
|
|
|
uid_attribute: uid_attribute
|
|
|
|
}
|
|
|
|
|
|
|
|
instance = described_class.new(config, ldap: mocked_ldap)
|
|
|
|
|
|
|
|
expect(instance.uid_attribute).to eq(uid_attribute)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates own Ldap instance if none given' do
|
|
|
|
expect(Ldap).to receive(:new)
|
2019-04-15 01:41:17 +00:00
|
|
|
|
2021-07-16 13:29:38 +00:00
|
|
|
described_class.new
|
2017-04-19 10:09:54 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-06-23 11:35:27 +00:00
|
|
|
describe 'instance methods' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2017-10-01 12:25:52 +00:00
|
|
|
let(:initialization_config) do
|
2017-04-19 10:09:54 +00:00
|
|
|
{
|
2018-01-22 15:53:53 +00:00
|
|
|
uid_attribute: 'objectguid',
|
2017-04-19 10:09:54 +00:00
|
|
|
filter: '(objectClass=user)',
|
|
|
|
}
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2017-10-01 12:25:52 +00:00
|
|
|
let(:instance) do
|
2017-04-19 10:09:54 +00:00
|
|
|
described_class.new(initialization_config, ldap: mocked_ldap)
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2020-02-18 19:51:31 +00:00
|
|
|
describe '#valid?' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2021-09-08 10:02:59 +00:00
|
|
|
shared_examples 'validates credentials' do
|
|
|
|
it 'validates username and password' do
|
|
|
|
connection = double
|
|
|
|
allow(mocked_ldap).to receive(:connection).and_return(connection)
|
|
|
|
|
|
|
|
build(:ldap_entry)
|
|
|
|
|
|
|
|
allow(mocked_ldap).to receive(:base_dn)
|
|
|
|
allow(connection).to receive(:bind_as).and_return(true)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2021-09-08 10:02:59 +00:00
|
|
|
expect(instance.valid?('example_username', 'password')).to be true
|
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2021-09-08 10:02:59 +00:00
|
|
|
it 'fails for invalid credentials' do
|
|
|
|
connection = double
|
|
|
|
allow(mocked_ldap).to receive(:connection).and_return(connection)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2021-09-08 10:02:59 +00:00
|
|
|
build(:ldap_entry)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2021-09-08 10:02:59 +00:00
|
|
|
allow(mocked_ldap).to receive(:base_dn)
|
|
|
|
allow(connection).to receive(:bind_as).and_return(false)
|
|
|
|
|
|
|
|
expect(instance.valid?('example_username', 'wrong_password')).to be false
|
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
end
|
|
|
|
|
2021-09-08 10:02:59 +00:00
|
|
|
it 'responds to #valid?' do
|
|
|
|
expect(instance).to respond_to(:valid?)
|
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2021-09-08 10:02:59 +00:00
|
|
|
it_behaves_like 'validates credentials'
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2021-09-08 10:02:59 +00:00
|
|
|
context 'with a user_filter inside of the config' do
|
|
|
|
let(:initialization_config) do
|
|
|
|
{
|
|
|
|
uid_attribute: 'objectguid',
|
|
|
|
filter: '(objectClass=user)',
|
|
|
|
user_filter: '(cn=example)'
|
|
|
|
}
|
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2021-09-08 10:02:59 +00:00
|
|
|
it_behaves_like 'validates credentials'
|
2017-04-19 10:09:54 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-18 19:51:31 +00:00
|
|
|
describe '#attributes' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
it 'responds to #attributes' do
|
|
|
|
expect(instance).to respond_to(:attributes)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'lists user attributes with example values' do
|
|
|
|
ldap_entry = build(:ldap_entry)
|
|
|
|
|
|
|
|
# selectable attribute
|
|
|
|
ldap_entry['mail'] = 'test@example.com'
|
|
|
|
|
2021-07-12 13:18:31 +00:00
|
|
|
# filtered attribute
|
2017-04-19 10:09:54 +00:00
|
|
|
ldap_entry['lastlogon'] = DateTime.current
|
|
|
|
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:search).and_yield(ldap_entry)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
attributes = instance.attributes
|
|
|
|
|
|
|
|
expected_attributes = {
|
|
|
|
dn: String,
|
|
|
|
mail: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
expect(attributes).to include(expected_attributes)
|
|
|
|
expect(attributes).not_to include(:lastlogon)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-18 19:51:31 +00:00
|
|
|
describe '#filter' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2017-10-01 12:25:52 +00:00
|
|
|
let(:initialization_config) do
|
2017-04-19 10:09:54 +00:00
|
|
|
{
|
2018-01-22 15:53:53 +00:00
|
|
|
uid_attribute: 'objectguid',
|
2017-04-19 10:09:54 +00:00
|
|
|
}
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
it 'responds to #filter' do
|
|
|
|
expect(instance).to respond_to(:filter)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'tries filters and returns first one with entries' do
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:entries?).and_return(true)
|
2017-04-19 10:09:54 +00:00
|
|
|
expect(instance.filter).to be_a(String)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fails if no filter found entries' do
|
|
|
|
allow(mocked_ldap).to receive(:entries?).and_return(false)
|
|
|
|
expect(instance.filter).to be nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-18 19:51:31 +00:00
|
|
|
describe '#uid_attribute' do
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2017-10-01 12:25:52 +00:00
|
|
|
let(:initialization_config) do
|
2017-04-19 10:09:54 +00:00
|
|
|
{
|
|
|
|
filter: '(objectClass=user)',
|
|
|
|
}
|
2017-10-01 12:25:52 +00:00
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
it 'responds to #uid_attribute' do
|
|
|
|
expect(instance).to respond_to(:uid_attribute)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'tries to find uid attribute in example attributes' do
|
|
|
|
ldap_entry = build(:ldap_entry)
|
|
|
|
|
|
|
|
# selectable attribute
|
2018-01-22 15:53:53 +00:00
|
|
|
ldap_entry['objectguid'] = 'f742b361-32c6-4a92-baaa-eaae7df657ee'
|
2017-04-19 10:09:54 +00:00
|
|
|
|
2020-10-22 13:57:01 +00:00
|
|
|
allow(mocked_ldap).to receive(:search).and_yield(ldap_entry)
|
2017-04-19 10:09:54 +00:00
|
|
|
|
|
|
|
expect(instance.uid_attribute).to be_a(String)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fails if no uid attribute could be found' do
|
|
|
|
expect(mocked_ldap).to receive(:search)
|
|
|
|
expect(instance.uid_attribute).to be nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2018-08-31 15:27:57 +00:00
|
|
|
|
|
|
|
# Each of these test cases depends on
|
|
|
|
# sample TCP transmission data recorded with TCR,
|
|
|
|
# stored in test/data/tcr_cassettes.
|
2021-06-23 11:35:27 +00:00
|
|
|
describe 'on mocked LDAP connections' do
|
2018-08-31 15:27:57 +00:00
|
|
|
around do |example|
|
2021-05-12 11:37:44 +00:00
|
|
|
cassette_name = example.description.gsub(%r{[^0-9A-Za-z.\-]+}, '_')
|
2018-08-31 15:27:57 +00:00
|
|
|
|
|
|
|
begin
|
|
|
|
original_tcr_format = TCR.configuration.format
|
|
|
|
TCR.configuration.format = 'marshal'
|
|
|
|
TCR.use_cassette("lib/ldap/user/#{cassette_name}") { example.run }
|
|
|
|
ensure
|
|
|
|
TCR.configuration.format = original_tcr_format
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-06-23 11:35:27 +00:00
|
|
|
describe 'attributes' do
|
2021-09-22 06:53:16 +00:00
|
|
|
subject(:user) { described_class.new(config, ldap: ldap) }
|
|
|
|
|
|
|
|
let(:ldap) { Ldap.new(config) }
|
2018-08-31 15:27:57 +00:00
|
|
|
let(:config) do
|
|
|
|
{ 'host_url' => 'ldap://localhost',
|
|
|
|
'options' => { 'dc=example,dc=org' => 'dc=example,dc=org' },
|
|
|
|
'option' => 'dc=example,dc=org',
|
|
|
|
'base_dn' => 'dc=example,dc=org',
|
|
|
|
'bind_user' => 'cn=admin,dc=example,dc=org',
|
|
|
|
'bind_pw' => 'admin' }.with_indifferent_access
|
|
|
|
end
|
|
|
|
|
|
|
|
# see https://github.com/zammad/zammad/issues/2140
|
|
|
|
#
|
|
|
|
# This method grabs sample values of user attributes on the LDAP server.
|
|
|
|
# It used to coerce ALL values to Unicode strings, including binary attributes
|
|
|
|
# (e.g., usersmimecertificate / msexchmailboxsecuritydescriptor),
|
|
|
|
# which led to valid Unicode gibberish (e.g., "\u0001\u0001\u0004...")
|
|
|
|
#
|
|
|
|
# When saving these values to the database,
|
|
|
|
# ActiveRecord::Store would convert them to binary (ASCII-8BIT) strings,
|
|
|
|
# which would then break #to_json with an Encoding::UndefinedConversion error.
|
|
|
|
it 'skips binary attributes (#2140)' do
|
2021-09-22 06:53:16 +00:00
|
|
|
Setting.set('ldap_config', user.attributes)
|
2018-08-31 15:27:57 +00:00
|
|
|
|
|
|
|
expect { Setting.get('ldap_config').to_json }
|
|
|
|
.not_to raise_error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-04-19 10:09:54 +00:00
|
|
|
end
|