user_spec.rb 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. # rails autoloading issue
  4. require 'ldap'
  5. require 'ldap/user'
  6. require 'tcr/net/ldap'
  7. RSpec.describe Ldap::User do
  8. let(:mocked_ldap) { double }
  9. describe '.uid_attribute' do
  10. it 'responds to .uid_attribute' do
  11. expect(described_class).to respond_to(:uid_attribute)
  12. end
  13. it 'returns uid attribute string from given attribute strucutre' do
  14. attributes = {
  15. objectguid: 'TEST',
  16. custom: 'value',
  17. }
  18. expect(described_class.uid_attribute(attributes)).to eq('objectguid')
  19. end
  20. it 'returns nil if no attribute could be found' do
  21. attributes = {
  22. custom: 'value',
  23. }
  24. expect(described_class.uid_attribute(attributes)).to be_nil
  25. end
  26. end
  27. # required as 'let' to perform test based
  28. # expectations and reuse it in 'let' instance
  29. # as additional parameter
  30. describe 'initialization config parameters' do
  31. it 'reuses given Ldap instance if given' do
  32. expect(Ldap).not_to receive(:new)
  33. described_class.new(create(:ldap_source), ldap: mocked_ldap)
  34. end
  35. it 'takes optional filter' do
  36. filter = '(objectClass=custom)'
  37. config = {
  38. filter: filter
  39. }
  40. instance = described_class.new(config, ldap: mocked_ldap)
  41. expect(instance.filter).to eq(filter)
  42. end
  43. it 'takes optional uid_attribute' do
  44. uid_attribute = 'objectguid'
  45. config = {
  46. uid_attribute: uid_attribute
  47. }
  48. instance = described_class.new(config, ldap: mocked_ldap)
  49. expect(instance.uid_attribute).to eq(uid_attribute)
  50. end
  51. it 'creates own Ldap instance if none given' do
  52. expect(Ldap).to receive(:new)
  53. described_class.new(create(:ldap_source))
  54. end
  55. end
  56. describe 'instance methods' do
  57. let(:initialization_config) do
  58. {
  59. uid_attribute: 'objectguid',
  60. filter: '(objectClass=user)',
  61. }
  62. end
  63. let(:instance) do
  64. described_class.new(initialization_config, ldap: mocked_ldap)
  65. end
  66. describe '#valid?' do
  67. shared_examples 'validates credentials' do
  68. it 'validates username and password' do
  69. connection = double
  70. allow(mocked_ldap).to receive(:connection).and_return(connection)
  71. build(:ldap_entry)
  72. allow(mocked_ldap).to receive(:base_dn)
  73. allow(connection).to receive(:bind_as).and_return(true)
  74. expect(instance.valid?('example_username', 'password')).to be true
  75. end
  76. it 'fails for invalid credentials' do
  77. connection = double
  78. allow(mocked_ldap).to receive(:connection).and_return(connection)
  79. build(:ldap_entry)
  80. allow(mocked_ldap).to receive(:base_dn)
  81. allow(connection).to receive(:bind_as).and_return(false)
  82. expect(instance.valid?('example_username', 'wrong_password')).to be false
  83. end
  84. end
  85. it 'responds to #valid?' do
  86. expect(instance).to respond_to(:valid?)
  87. end
  88. it_behaves_like 'validates credentials'
  89. context 'with a user_filter inside of the config' do
  90. let(:initialization_config) do
  91. {
  92. uid_attribute: 'objectguid',
  93. filter: '(objectClass=user)',
  94. user_filter: '(cn=example)'
  95. }
  96. end
  97. it_behaves_like 'validates credentials'
  98. end
  99. end
  100. describe '#attributes' do
  101. it 'responds to #attributes' do
  102. expect(instance).to respond_to(:attributes)
  103. end
  104. it 'lists user attributes with example values' do
  105. ldap_entry = build(:ldap_entry)
  106. # selectable attribute
  107. ldap_entry['mail'] = 'test@example.com'
  108. # filtered attribute
  109. ldap_entry['lastlogon'] = DateTime.current
  110. allow(mocked_ldap).to receive(:search).and_yield(ldap_entry)
  111. attributes = instance.attributes
  112. expected_attributes = {
  113. dn: String,
  114. mail: String,
  115. }
  116. expect(attributes).to include(expected_attributes)
  117. expect(attributes).not_to include(:lastlogon)
  118. end
  119. end
  120. describe '#filter' do
  121. let(:initialization_config) do
  122. {
  123. uid_attribute: 'objectguid',
  124. }
  125. end
  126. it 'responds to #filter' do
  127. expect(instance).to respond_to(:filter)
  128. end
  129. it 'tries filters and returns first one with entries' do
  130. allow(mocked_ldap).to receive(:entries?).and_return(true)
  131. expect(instance.filter).to be_a(String)
  132. end
  133. it 'fails if no filter found entries' do
  134. allow(mocked_ldap).to receive(:entries?).and_return(false)
  135. expect(instance.filter).to be_nil
  136. end
  137. end
  138. describe '#uid_attribute' do
  139. let(:initialization_config) do
  140. {
  141. filter: '(objectClass=user)',
  142. }
  143. end
  144. it 'responds to #uid_attribute' do
  145. expect(instance).to respond_to(:uid_attribute)
  146. end
  147. it 'tries to find uid attribute in example attributes' do
  148. ldap_entry = build(:ldap_entry)
  149. # selectable attribute
  150. ldap_entry['objectguid'] = 'f742b361-32c6-4a92-baaa-eaae7df657ee'
  151. allow(mocked_ldap).to receive(:search).and_yield(ldap_entry)
  152. expect(instance.uid_attribute).to be_a(String)
  153. end
  154. it 'fails if no uid attribute could be found' do
  155. expect(mocked_ldap).to receive(:search)
  156. expect(instance.uid_attribute).to be_nil
  157. end
  158. end
  159. end
  160. # Each of these test cases depends on
  161. # sample TCP transmission data recorded with TCR,
  162. # stored in test/data/tcr_cassettes.
  163. describe 'on mocked LDAP connections' do
  164. around do |example|
  165. cassette_name = example.description.gsub(%r{[^0-9A-Za-z.-]+}, '_')
  166. begin
  167. original_tcr_format = TCR.configuration.format
  168. TCR.configuration.format = 'marshal'
  169. TCR.use_cassette("lib/ldap/user/#{cassette_name}") { example.run }
  170. ensure
  171. TCR.configuration.format = original_tcr_format
  172. end
  173. end
  174. describe 'attributes' do
  175. subject(:user) { described_class.new(config, ldap: ldap) }
  176. let(:ldap) { Ldap.new(config) }
  177. let(:config) do
  178. { 'host' => 'localhost',
  179. 'ssl' => 'off',
  180. 'options' => { 'dc=example,dc=org' => 'dc=example,dc=org' },
  181. 'option' => 'dc=example,dc=org',
  182. 'base_dn' => 'dc=example,dc=org',
  183. 'bind_user' => 'cn=admin,dc=example,dc=org',
  184. 'bind_pw' => 'admin' }.with_indifferent_access
  185. end
  186. # see https://github.com/zammad/zammad/issues/2140
  187. #
  188. # This method grabs sample values of user attributes on the LDAP server.
  189. # It used to coerce ALL values to Unicode strings, including binary attributes
  190. # (e.g., usersmimecertificate / msexchmailboxsecuritydescriptor),
  191. # which led to valid Unicode gibberish (e.g., "\u0001\u0001\u0004...")
  192. #
  193. # When saving these values to the database,
  194. # ActiveRecord::Store would convert them to binary (ASCII-8BIT) strings,
  195. # which would then break #to_json with an Encoding::UndefinedConversion error.
  196. it 'skips binary attributes (#2140)' do
  197. source = create(:ldap_source)
  198. source.update(preferences: user.attributes)
  199. expect { source.preferences.to_json }
  200. .not_to raise_error
  201. end
  202. end
  203. end
  204. end