api_auth_spec.rb 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe 'Api Auth', type: :request do
  4. around do |example|
  5. orig = ActionController::Base.allow_forgery_protection
  6. begin
  7. ActionController::Base.allow_forgery_protection = true
  8. example.run
  9. ensure
  10. ActionController::Base.allow_forgery_protection = orig
  11. end
  12. end
  13. let(:admin) do
  14. create(:admin)
  15. end
  16. let(:agent) do
  17. create(:agent)
  18. end
  19. let(:customer) do
  20. create(:customer)
  21. end
  22. before do
  23. stub_const('Auth::BRUTE_FORCE_SLEEP', 0)
  24. end
  25. describe 'request handling' do
  26. it 'does basic auth - admin' do
  27. Setting.set('api_password_access', false)
  28. authenticated_as(admin)
  29. get '/api/v1/sessions', params: {}, as: :json
  30. expect(response).to have_http_status(:forbidden)
  31. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  32. expect(json_response).to be_a(Hash)
  33. expect(json_response['error']).to eq('API password access disabled!')
  34. Setting.set('api_password_access', true)
  35. get '/api/v1/sessions', params: {}, as: :json
  36. expect(response).to have_http_status(:ok)
  37. expect(response.header['Access-Control-Allow-Origin']).to eq('*')
  38. expect(response.header['Cache-Control']).to eq('max-age=0, private, must-revalidate')
  39. expect(json_response).to be_a(Hash)
  40. expect(json_response).to be_truthy
  41. end
  42. it 'does basic auth - agent' do
  43. Setting.set('api_password_access', false)
  44. authenticated_as(agent)
  45. get '/api/v1/tickets', params: {}, as: :json
  46. expect(response).to have_http_status(:forbidden)
  47. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  48. expect(json_response).to be_a(Hash)
  49. expect(json_response['error']).to eq('API password access disabled!')
  50. Setting.set('api_password_access', true)
  51. get '/api/v1/tickets', params: {}, as: :json
  52. expect(response).to have_http_status(:ok)
  53. expect(response.header['Access-Control-Allow-Origin']).to eq('*')
  54. expect(response.header['Cache-Control']).to eq('max-age=0, private, must-revalidate')
  55. expect(json_response).to be_a(Array)
  56. expect(json_response).to be_truthy
  57. end
  58. it 'does basic auth - customer' do
  59. Setting.set('api_password_access', false)
  60. authenticated_as(customer)
  61. get '/api/v1/tickets', params: {}, as: :json
  62. expect(response).to have_http_status(:forbidden)
  63. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  64. expect(json_response).to be_a(Hash)
  65. expect(json_response['error']).to eq('API password access disabled!')
  66. Setting.set('api_password_access', true)
  67. get '/api/v1/tickets', params: {}, as: :json
  68. expect(response).to have_http_status(:ok)
  69. expect(response.header['Access-Control-Allow-Origin']).to eq('*')
  70. expect(response.header['Cache-Control']).to eq('max-age=0, private, must-revalidate')
  71. expect(json_response).to be_a(Array)
  72. expect(json_response).to be_truthy
  73. end
  74. context 'when using BasicAuth with TwoFactor' do
  75. let!(:two_factor_pref) { create(:user_two_factor_preference, :authenticator_app, user: admin) }
  76. it 'rejects the log-in' do
  77. two_factor_pref
  78. authenticated_as(admin)
  79. Setting.set('api_password_access', true)
  80. get '/api/v1/sessions', params: {}, as: :json
  81. expect(response).to have_http_status(:unauthorized)
  82. end
  83. end
  84. it 'does token auth - admin', last_admin_check: false do
  85. admin_token = create(
  86. :token,
  87. action: 'api',
  88. persistent: true,
  89. user_id: admin.id,
  90. preferences: {
  91. permission: ['admin.session'],
  92. },
  93. )
  94. authenticated_as(admin, token: admin_token)
  95. Setting.set('api_token_access', false)
  96. get '/api/v1/sessions', params: {}, as: :json
  97. expect(response).to have_http_status(:forbidden)
  98. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  99. expect(json_response).to be_a(Hash)
  100. expect(json_response['error']).to eq('API token access disabled!')
  101. Setting.set('api_token_access', true)
  102. get '/api/v1/sessions', params: {}, as: :json
  103. expect(response).to have_http_status(:ok)
  104. expect(response.header['Access-Control-Allow-Origin']).to eq('*')
  105. expect(response.header['Cache-Control']).to eq('max-age=0, private, must-revalidate')
  106. expect(json_response).to be_a(Hash)
  107. expect(json_response).to be_truthy
  108. admin_token.preferences[:permission] = ['admin.session_not_existing']
  109. admin_token.save!
  110. get '/api/v1/sessions', params: {}, as: :json
  111. expect(response).to have_http_status(:forbidden)
  112. expect(json_response).to be_a(Hash)
  113. expect(json_response['error']).to eq('Not authorized (token)!')
  114. admin_token.preferences[:permission] = []
  115. admin_token.save!
  116. get '/api/v1/sessions', params: {}, as: :json
  117. expect(response).to have_http_status(:forbidden)
  118. expect(json_response).to be_a(Hash)
  119. expect(json_response['error']).to eq('Not authorized (token)!')
  120. admin.active = false
  121. admin.save!
  122. get '/api/v1/sessions', params: {}, as: :json
  123. expect(response).to have_http_status(:unauthorized)
  124. expect(json_response).to be_a(Hash)
  125. expect(json_response['error']).to eq('Login failed. Have you double-checked your credentials and completed the email verification step?')
  126. admin_token.preferences[:permission] = ['admin.session']
  127. admin_token.save!
  128. get '/api/v1/sessions', params: {}, as: :json
  129. expect(response).to have_http_status(:unauthorized)
  130. expect(json_response).to be_a(Hash)
  131. expect(json_response['error']).to eq('Login failed. Have you double-checked your credentials and completed the email verification step?')
  132. admin.active = true
  133. admin.save!
  134. get '/api/v1/sessions', params: {}, as: :json
  135. expect(response).to have_http_status(:ok)
  136. expect(json_response).to be_a(Hash)
  137. expect(json_response).to be_truthy
  138. get '/api/v1/roles', params: {}, as: :json
  139. expect(response).to have_http_status(:forbidden)
  140. expect(json_response).to be_a(Hash)
  141. expect(json_response['error']).to eq('Not authorized (token)!')
  142. admin_token.preferences[:permission] = ['admin.session_not_existing', 'admin.role']
  143. admin_token.save!
  144. get '/api/v1/roles', params: {}, as: :json
  145. expect(response).to have_http_status(:ok)
  146. expect(json_response).to be_a(Array)
  147. expect(json_response).to be_truthy
  148. admin_token.preferences[:permission] = ['ticket.agent']
  149. admin_token.save!
  150. get '/api/v1/organizations', params: {}, as: :json
  151. expect(response).to have_http_status(:ok)
  152. expect(json_response).to be_a(Array)
  153. expect(json_response).to be_truthy
  154. name = "some org name #{SecureRandom.uuid}"
  155. post '/api/v1/organizations', params: { name: name }, as: :json
  156. expect(response).to have_http_status(:created)
  157. expect(json_response).to be_a(Hash)
  158. expect(json_response['name']).to eq(name)
  159. expect(json_response).to be_truthy
  160. name = "some org name #{SecureRandom.uuid} - 2"
  161. put "/api/v1/organizations/#{json_response['id']}", params: { name: name }, as: :json
  162. expect(response).to have_http_status(:ok)
  163. expect(json_response).to be_a(Hash)
  164. expect(json_response['name']).to eq(name)
  165. expect(json_response).to be_truthy
  166. admin_token.preferences[:permission] = ['admin.organization']
  167. admin_token.save!
  168. get '/api/v1/organizations', params: {}, as: :json
  169. expect(response).to have_http_status(:ok)
  170. expect(json_response).to be_a(Array)
  171. expect(json_response).to be_truthy
  172. name = "some org name #{SecureRandom.uuid}"
  173. post '/api/v1/organizations', params: { name: name }, as: :json
  174. expect(response).to have_http_status(:created)
  175. expect(json_response).to be_a(Hash)
  176. expect(json_response['name']).to eq(name)
  177. expect(json_response).to be_truthy
  178. name = "some org name #{SecureRandom.uuid} - 2"
  179. put "/api/v1/organizations/#{json_response['id']}", params: { name: name }, as: :json
  180. expect(response).to have_http_status(:ok)
  181. expect(json_response).to be_a(Hash)
  182. expect(json_response['name']).to eq(name)
  183. expect(json_response).to be_truthy
  184. admin_token.preferences[:permission] = ['admin']
  185. admin_token.save!
  186. get '/api/v1/organizations', params: {}, as: :json
  187. expect(response).to have_http_status(:ok)
  188. expect(json_response).to be_a(Array)
  189. expect(json_response).to be_truthy
  190. name = "some org name #{SecureRandom.uuid}"
  191. post '/api/v1/organizations', params: { name: name }, as: :json
  192. expect(response).to have_http_status(:created)
  193. expect(json_response).to be_a(Hash)
  194. expect(json_response['name']).to eq(name)
  195. expect(json_response).to be_truthy
  196. name = "some org name #{SecureRandom.uuid} - 2"
  197. put "/api/v1/organizations/#{json_response['id']}", params: { name: name }, as: :json
  198. expect(response).to have_http_status(:ok)
  199. expect(json_response).to be_a(Hash)
  200. expect(json_response['name']).to eq(name)
  201. expect(json_response).to be_truthy
  202. end
  203. it 'does token auth - agent' do
  204. agent_token = create(
  205. :token,
  206. action: 'api',
  207. persistent: true,
  208. user_id: agent.id,
  209. )
  210. authenticated_as(agent, token: agent_token)
  211. Setting.set('api_token_access', false)
  212. get '/api/v1/tickets', params: {}, as: :json
  213. expect(response).to have_http_status(:forbidden)
  214. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  215. expect(json_response).to be_a(Hash)
  216. expect(json_response['error']).to eq('API token access disabled!')
  217. Setting.set('api_token_access', true)
  218. get '/api/v1/tickets', params: {}, as: :json
  219. expect(response).to have_http_status(:ok)
  220. expect(response.header['Access-Control-Allow-Origin']).to eq('*')
  221. expect(response.header['Cache-Control']).to eq('max-age=0, private, must-revalidate')
  222. expect(json_response).to be_a(Array)
  223. expect(json_response).to be_truthy
  224. get '/api/v1/organizations', params: {}, as: :json
  225. expect(response).to have_http_status(:ok)
  226. expect(json_response).to be_a(Array)
  227. expect(json_response).to be_truthy
  228. name = "some org name #{SecureRandom.uuid}"
  229. post '/api/v1/organizations', params: { name: name }, as: :json
  230. expect(response).to have_http_status(:forbidden)
  231. end
  232. it 'does token auth - customer' do
  233. customer_token = create(
  234. :token,
  235. action: 'api',
  236. persistent: true,
  237. user_id: customer.id,
  238. )
  239. authenticated_as(customer, token: customer_token)
  240. Setting.set('api_token_access', false)
  241. get '/api/v1/tickets', params: {}, as: :json
  242. expect(response).to have_http_status(:forbidden)
  243. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  244. expect(json_response).to be_a(Hash)
  245. expect(json_response['error']).to eq('API token access disabled!')
  246. Setting.set('api_token_access', true)
  247. get '/api/v1/tickets', params: {}, as: :json
  248. expect(response.header['Access-Control-Allow-Origin']).to eq('*')
  249. expect(response.header['Cache-Control']).to eq('max-age=0, private, must-revalidate')
  250. expect(response).to have_http_status(:ok)
  251. expect(json_response).to be_a(Array)
  252. expect(json_response).to be_truthy
  253. get '/api/v1/organizations', params: {}, as: :json
  254. expect(response).to have_http_status(:ok)
  255. expect(json_response).to be_a(Array)
  256. expect(json_response).to be_truthy
  257. name = "some org name #{SecureRandom.uuid}"
  258. post '/api/v1/organizations', params: { name: name }, as: :json
  259. expect(response).to have_http_status(:forbidden)
  260. end
  261. it 'does token auth - invalid user - admin', last_admin_check: false do
  262. admin_token = create(
  263. :token,
  264. action: 'api',
  265. persistent: true,
  266. user_id: admin.id,
  267. )
  268. authenticated_as(admin, token: admin_token)
  269. admin.active = false
  270. admin.save!
  271. Setting.set('api_token_access', false)
  272. get '/api/v1/sessions', params: {}, as: :json
  273. expect(response).to have_http_status(:forbidden)
  274. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  275. expect(json_response).to be_a(Hash)
  276. expect(json_response['error']).to eq('API token access disabled!')
  277. Setting.set('api_token_access', true)
  278. get '/api/v1/sessions', params: {}, as: :json
  279. expect(response).to have_http_status(:unauthorized)
  280. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  281. expect(json_response).to be_a(Hash)
  282. expect(json_response['error']).to eq('Login failed. Have you double-checked your credentials and completed the email verification step?')
  283. end
  284. it 'does token auth - expired' do
  285. Setting.set('api_token_access', true)
  286. admin_token = create(
  287. :token,
  288. action: 'api',
  289. persistent: true,
  290. user_id: admin.id,
  291. expires_at: Time.zone.today
  292. )
  293. authenticated_as(admin, token: admin_token)
  294. get '/api/v1/tickets', params: {}, as: :json
  295. expect(response).to have_http_status(:unauthorized)
  296. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  297. expect(json_response).to be_a(Hash)
  298. expect(json_response['error']).to eq('Not authorized (token expired)!')
  299. admin_token.reload
  300. expect(admin_token.last_used_at).to be_within(1.second).of(Time.zone.now)
  301. end
  302. it 'does token auth - not expired' do
  303. Setting.set('api_token_access', true)
  304. admin_token = create(
  305. :token,
  306. action: 'api',
  307. persistent: true,
  308. user_id: admin.id,
  309. expires_at: Time.zone.tomorrow
  310. )
  311. authenticated_as(admin, token: admin_token)
  312. get '/api/v1/tickets', params: {}, as: :json
  313. expect(response).to have_http_status(:ok)
  314. expect(response.header['Access-Control-Allow-Origin']).to eq('*')
  315. expect(response.header['Cache-Control']).to eq('max-age=0, private, must-revalidate')
  316. expect(json_response).to be_a(Array)
  317. expect(json_response).to be_truthy
  318. admin_token.reload
  319. expect(admin_token.last_used_at).to be_within(1.second).of(Time.zone.now)
  320. end
  321. it 'does session auth - admin' do
  322. admin = create(:admin)
  323. get '/'
  324. token = response.headers['CSRF-TOKEN']
  325. post '/api/v1/signin', params: { username: admin.login, password: admin.password, fingerprint: '123456789' }, headers: { 'X-CSRF-Token' => token }
  326. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  327. expect(response).to have_http_status(:created)
  328. get '/api/v1/sessions', params: {}
  329. expect(response).to have_http_status(:ok)
  330. expect(response.header).not_to be_key('Access-Control-Allow-Origin')
  331. expect(json_response).to be_a(Hash)
  332. expect(json_response).to be_truthy
  333. end
  334. context 'when using session auth with TwoFactor' do
  335. let(:admin) { create(:admin) }
  336. let(:two_factor_method) { nil }
  337. let(:two_factor_payload) { nil }
  338. let(:code) { two_factor_pref.configuration[:code] }
  339. let!(:two_factor_pref) { create(:user_two_factor_preference, :authenticator_app, user: admin) }
  340. before do
  341. get '/'
  342. token = response.headers['CSRF-TOKEN']
  343. post '/api/v1/signin', params: { username: admin.login, password: admin.password, two_factor_method: two_factor_method, two_factor_payload: two_factor_payload, fingerprint: '123456789' }, headers: { 'X-CSRF-Token' => token }
  344. end
  345. context 'without two factor token' do
  346. it 'rejects the log-in' do
  347. expect(response).to have_http_status(:unprocessable_entity)
  348. end
  349. end
  350. context 'with wrong two factor token' do
  351. let(:two_factor_payload) { 'wrong' }
  352. let(:two_factor_method) { 'authenticator_app' }
  353. it 'rejects the log-in' do
  354. expect(response).to have_http_status(:unauthorized)
  355. end
  356. end
  357. context 'with correct two factor token' do
  358. let(:two_factor_payload) { code }
  359. let(:two_factor_method) { 'authenticator_app' }
  360. it 'accepts the log-in' do
  361. expect(response).to have_http_status(:created)
  362. end
  363. end
  364. end
  365. it 'does session auth - admin - only with valid CSRF token' do
  366. create(:admin, login: 'api-admin@example.com', password: 'adminpw')
  367. post '/api/v1/signin', params: { username: 'api-admin@example.com', password: 'adminpw', fingerprint: '123456789' }
  368. expect(response).to have_http_status(:unauthorized)
  369. end
  370. end
  371. end