user_spec.rb 6.6 KB

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