auth_spec.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. # Copyright (C) 2012-2024 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 'raises an error and increases the failed login count' do
  63. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed).and(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. begin
  68. instance.try(:valid!)
  69. rescue Auth::Error::AuthenticationFailed
  70. # no-op
  71. end
  72. # sleep receives the stubbed value.
  73. expect(instance).to have_received(:sleep).with(0)
  74. end
  75. end
  76. context 'with valid user and required two factor' do
  77. let!(:two_factor_pref) { create(:user_two_factor_preference, :authenticator_app, user: user) }
  78. let(:enabled) { true }
  79. before do
  80. Setting.set('two_factor_authentication_method_authenticator_app', enabled)
  81. end
  82. context 'without valid two factor token' do
  83. it 'raises an error and does not the failed login count' do
  84. expect { instance.valid! }.to raise_error(Auth::Error::TwoFactorRequired).and(not_change { user.reload.login_failed })
  85. end
  86. end
  87. context 'with an invalid two factor token' do
  88. let(:instance) { described_class.new(user.login, password, two_factor_method: 'authenticator_app', two_factor_payload: 'wrong') }
  89. it 'raises an error and does not increase the failed login count' do
  90. expect { instance.valid! }.to raise_error(Auth::Error::TwoFactorFailed).and(not_change { user.reload.login_failed })
  91. end
  92. context 'with disabled authenticator method' do
  93. let(:enabled) { false }
  94. it 'allows the log-in' do
  95. expect(instance.valid!).to be true
  96. end
  97. end
  98. end
  99. context 'with a valid two factor token' do
  100. let(:code) { two_factor_pref.configuration[:code] }
  101. let(:instance) { described_class.new(user.login, password, two_factor_method: 'authenticator_app', two_factor_payload: code) }
  102. it 'allows the log-in' do
  103. expect(instance.valid!).to be true
  104. end
  105. context 'with disabled authenticator method' do
  106. let(:enabled) { false }
  107. it 'allows the log-in' do
  108. expect(instance.valid!).to be true
  109. end
  110. end
  111. end
  112. end
  113. context 'with inactive user login' do
  114. let(:user) { create(:user, active: false) }
  115. it 'returns false' do
  116. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed)
  117. end
  118. end
  119. context 'with non-existent user login' do
  120. let(:instance) { described_class.new('not_existing', password) }
  121. it 'returns false' do
  122. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed)
  123. end
  124. end
  125. context 'with empty user login' do
  126. let(:instance) { described_class.new('', password) }
  127. it 'returns false' do
  128. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed)
  129. end
  130. end
  131. context 'when password is empty' do
  132. before do
  133. # Remove adapter from auth developer setting, to avoid execution for this test case, because of special empty
  134. # password handling in adapter.
  135. Setting.set('auth_developer', {})
  136. end
  137. context 'with empty password string' do
  138. let(:password) { '' }
  139. it 'returns false' do
  140. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed)
  141. end
  142. end
  143. shared_examples 'check empty password' do
  144. context 'when password is an empty string' do
  145. let(:password) { '' }
  146. it 'returns false' do
  147. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed)
  148. end
  149. end
  150. context 'when password is nil' do
  151. let(:password) { nil }
  152. it 'returns false' do
  153. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed)
  154. end
  155. end
  156. end
  157. context 'with empty password string when the stored password is an empty string' do
  158. before { user.update_column(:password, '') }
  159. include_examples 'check empty password'
  160. end
  161. context 'with empty password string when the stored hash represents an empty string' do
  162. before { user.update(password: PasswordHash.crypt('')) }
  163. include_examples 'check empty password'
  164. end
  165. end
  166. end
  167. context 'with a ldap user' do
  168. let(:password_ldap) { 'zammad_ldap' }
  169. let(:ldap_user) { instance_double(Ldap::User) }
  170. before do
  171. Setting.set('ldap_integration', true)
  172. allow(Ldap::User).to receive(:new).with(any_args).and_return(ldap_user)
  173. end
  174. shared_examples 'check empty password' do
  175. before do
  176. # Remove adapter from auth developer setting, to avoid execution for this test case, because of special empty
  177. # password handling in adapter.
  178. Setting.set('auth_developer', {})
  179. end
  180. context 'with empty password string' do
  181. let(:password) { '' }
  182. it 'returns false' do
  183. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed)
  184. end
  185. end
  186. context 'when password is nil' do
  187. let(:password) { nil }
  188. it 'returns false' do
  189. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed)
  190. end
  191. end
  192. end
  193. context 'with a ldap user without internal password' do
  194. let(:ldap_source) { create(:ldap_source) }
  195. let(:user) { create(:user, source: "Ldap::#{ldap_source.id}") }
  196. let(:password) { password_ldap }
  197. context 'with valid credentials' do
  198. before do
  199. allow(ldap_user).to receive(:valid?).with(any_args).and_return(true)
  200. end
  201. it 'returns true' do
  202. expect(instance.valid!).to be true
  203. end
  204. end
  205. context 'with invalid credentials' do
  206. let(:password) { 'wrong' }
  207. before do
  208. allow(ldap_user).to receive(:valid?).with(any_args).and_return(false)
  209. end
  210. it 'raises an error and does not increase the failed login count' do
  211. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed).and(not_change { user.reload.login_failed })
  212. end
  213. end
  214. include_examples 'check empty password'
  215. end
  216. context 'with a ldap user which also has a internal password' do
  217. let(:user) { create(:user, source: 'Ldap', password: password) }
  218. let(:password) { password_ldap }
  219. context 'with valid ldap credentials' do
  220. before do
  221. allow(ldap_user).to receive(:valid?).with(any_args).and_return(true)
  222. end
  223. it 'returns true' do
  224. expect(instance.valid!).to be true
  225. end
  226. end
  227. context 'with invalid ldap credentials' do
  228. let(:instance) { described_class.new(user.login, 'wrong') }
  229. before do
  230. allow(ldap_user).to receive(:valid?).with(any_args).and_return(false)
  231. end
  232. it 'raises an error and does not increase the failed login count' do
  233. expect { instance.valid! }.to raise_error(Auth::Error::AuthenticationFailed).and(change { user.reload.login_failed }.from(0).to(1))
  234. end
  235. end
  236. context 'with valid internal credentials' do
  237. before do
  238. allow(ldap_user).to receive(:valid?).with(any_args).and_return(false)
  239. end
  240. it 'returns true' do
  241. expect(instance.valid!).to be true
  242. end
  243. end
  244. include_examples 'check empty password'
  245. end
  246. end
  247. end
  248. end