auth_spec.rb 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Auth do
  4. let(:password) { 'zammad' }
  5. let(:user) { create(:user, password: password) }
  6. let(:instance) { described_class.new(user.login, password) }
  7. describe '.valid?' do
  8. it 'responds to valid?' do
  9. expect(instance).to respond_to(:valid?)
  10. end
  11. context 'with an internal user' do
  12. context 'with valid credentials' do
  13. it 'check for valid credentials' do
  14. expect(instance.valid?).to be true
  15. end
  16. it 'check for not increased failed login count' do
  17. expect { instance.valid? }.not_to change { user.reload.login_failed }
  18. end
  19. context 'when not case-sensitive' do
  20. let(:instance) { described_class.new(user.login.upcase, password) }
  21. it 'returns true' do
  22. expect(instance.valid?).to be true
  23. end
  24. end
  25. context 'when email is used' do
  26. let(:instance) { described_class.new(user.email, password) }
  27. it 'check for valid credentials' do
  28. expect(instance.valid?).to be true
  29. end
  30. end
  31. context 'when previous login was' do
  32. context 'when never logged in' do
  33. it 'updates #last_login and #updated_at' do
  34. expect { instance.valid? }.to change { user.reload.last_login }.and change { user.reload.updated_at }
  35. end
  36. end
  37. context 'when less than 10 minutes ago' do
  38. before do
  39. instance.valid?
  40. travel 9.minutes
  41. end
  42. it 'does not update #last_login and #updated_at' do
  43. expect { instance.valid? }.to not_change { user.reload.last_login }.and not_change { user.reload.updated_at }
  44. end
  45. end
  46. context 'when more than 10 minutes ago' do
  47. before do
  48. instance.valid?
  49. travel 11.minutes
  50. end
  51. it 'updates #last_login and #updated_at' do
  52. expect { instance.valid? }.to change { user.reload.last_login }.and change { user.reload.updated_at }
  53. end
  54. end
  55. end
  56. end
  57. context 'with valid user and invalid password' do
  58. let(:instance) { described_class.new(user.login, 'wrong') }
  59. it 'check for invalid credentials' do
  60. expect(instance.valid?).to be false
  61. end
  62. it 'check for increased failed login count' do
  63. expect { instance.valid? }.to change { user.reload.login_failed }.from(0).to(1)
  64. end
  65. it 'failed login avoids brute force attack' do
  66. allow(instance).to receive(:sleep)
  67. instance.valid?
  68. expect(instance).to have_received(:sleep).with(1)
  69. end
  70. end
  71. context 'with inactive user login' do
  72. let(:user) { create(:user, active: false) }
  73. it 'returns false' do
  74. expect(instance.valid?).to be false
  75. end
  76. end
  77. context 'with non-existent user login' do
  78. let(:instance) { described_class.new('not_existing', password) }
  79. it 'returns false' do
  80. expect(instance.valid?).to be false
  81. end
  82. end
  83. context 'with empty user login' do
  84. let(:instance) { described_class.new('', password) }
  85. it 'returns false' do
  86. expect(instance.valid?).to be false
  87. end
  88. end
  89. context 'when password is empty' do
  90. before do
  91. # Remove adapter from auth developer setting, to avoid execution for this test case, because of special empty
  92. # password handling in adapter.
  93. Setting.set('auth_developer', {})
  94. end
  95. context 'with empty password string' do
  96. let(:password) { '' }
  97. it 'returns false' do
  98. expect(instance.valid?).to be false
  99. end
  100. end
  101. shared_examples 'check empty password' do
  102. context 'when password is an empty string' do
  103. let(:password) { '' }
  104. it 'returns false' do
  105. expect(instance.valid?).to be false
  106. end
  107. end
  108. context 'when password is nil' do
  109. let(:password) { nil }
  110. it 'returns false' do
  111. expect(instance.valid?).to be false
  112. end
  113. end
  114. end
  115. context 'with empty password string when the stored password is an empty string' do
  116. before { user.update_column(:password, '') }
  117. include_examples 'check empty password'
  118. end
  119. context 'with empty password string when the stored hash represents an empty string' do
  120. before { user.update(password: PasswordHash.crypt('')) }
  121. include_examples 'check empty password'
  122. end
  123. end
  124. end
  125. context 'with a ldap user' do
  126. let(:password_ldap) { 'zammad_ldap' }
  127. let(:ldap_user) { instance_double(Ldap::User) }
  128. before do
  129. Setting.set('ldap_integration', true)
  130. allow(Ldap::User).to receive(:new).with(any_args).and_return(ldap_user)
  131. end
  132. shared_examples 'check empty password' do
  133. before do
  134. # Remove adapter from auth developer setting, to avoid execution for this test case, because of special empty
  135. # password handling in adapter.
  136. Setting.set('auth_developer', {})
  137. end
  138. context 'with empty password string' do
  139. let(:password) { '' }
  140. it 'returns false' do
  141. expect(instance.valid?).to be false
  142. end
  143. end
  144. context 'when password is nil' do
  145. let(:password) { nil }
  146. it 'returns false' do
  147. expect(instance.valid?).to be false
  148. end
  149. end
  150. end
  151. context 'with a ldap user without internal password' do
  152. let(:user) { create(:user, source: 'Ldap') }
  153. let(:password) { password_ldap }
  154. context 'with valid credentials' do
  155. before do
  156. allow(ldap_user).to receive(:valid?).with(any_args).and_return(true)
  157. end
  158. it 'returns true' do
  159. expect(instance.valid?).to be true
  160. end
  161. end
  162. context 'with invalid credentials' do
  163. let(:password) { 'wrong' }
  164. before do
  165. allow(ldap_user).to receive(:valid?).with(any_args).and_return(false)
  166. end
  167. it 'returns false' do
  168. expect(instance.valid?).to be false
  169. end
  170. it 'check for not increased failed login count' do
  171. expect { instance.valid? }.not_to change { user.reload.login_failed }
  172. end
  173. end
  174. include_examples 'check empty password'
  175. end
  176. context 'with a ldap user which also has a internal password' do
  177. let(:user) { create(:user, source: 'Ldap', password: password) }
  178. let(:password) { password_ldap }
  179. context 'with valid ldap credentials' do
  180. before do
  181. allow(ldap_user).to receive(:valid?).with(any_args).and_return(true)
  182. end
  183. it 'returns true' do
  184. expect(instance.valid?).to be true
  185. end
  186. end
  187. context 'with invalid ldap credentials' do
  188. let(:instance) { described_class.new(user.login, 'wrong') }
  189. before do
  190. allow(ldap_user).to receive(:valid?).with(any_args).and_return(false)
  191. end
  192. it 'returns false' do
  193. expect(instance.valid?).to be false
  194. end
  195. it 'check for not increased failed login count' do
  196. expect { instance.valid? }.to change { user.reload.login_failed }.from(0).to(1)
  197. end
  198. end
  199. context 'with valid internal credentials' do
  200. before do
  201. allow(ldap_user).to receive(:valid?).with(any_args).and_return(false)
  202. end
  203. it 'returns true' do
  204. expect(instance.valid?).to be true
  205. end
  206. end
  207. include_examples 'check empty password'
  208. end
  209. end
  210. end
  211. end