auth_spec.rb 8.0 KB

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