auth_spec.rb 9.6 KB

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