token_spec.rb 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Token, type: :model do
  4. subject(:token) { create(:password_reset_token, user: user) }
  5. let(:user) { create(:user) }
  6. describe '.check' do
  7. context 'with name and action matching existing token' do
  8. it 'returns the token’s user' do
  9. expect(described_class.check(action: token.action, token: token.token)).to eq(token.user)
  10. end
  11. end
  12. context 'with invalid name' do
  13. it 'returns nil' do
  14. expect(described_class.check(action: token.action, token: '1NV4L1D')).to be_nil
  15. end
  16. end
  17. context 'with invalid action' do
  18. it 'returns nil' do
  19. expect(described_class.check(action: 'PasswordReset_NotExisting', token: token.token)).to be_nil
  20. end
  21. end
  22. describe 'persistence handling' do
  23. context 'for persistent token' do
  24. subject(:token) { create(:ical_token, persistent: true, created_at: created_at) }
  25. context 'at any time' do
  26. let(:created_at) { 1.month.ago }
  27. it 'returns the token’s user' do
  28. expect(described_class.check(action: token.action, token: token.token)).to eq(token.user)
  29. end
  30. it 'does not delete the token' do
  31. token # create token
  32. expect { described_class.check(action: token.action, token: token.token) }
  33. .not_to change(described_class, :count)
  34. end
  35. end
  36. end
  37. context 'for non-persistent token' do
  38. subject(:token) { create(:password_reset_token, persistent: false, created_at: created_at) }
  39. context 'less than one day after creation' do
  40. let(:created_at) { 1.day.ago + 5 }
  41. it 'returns the token’s user' do
  42. expect(described_class.check(action: token.action, token: token.token)).to eq(token.user)
  43. end
  44. it 'does not delete the token' do
  45. token # create token
  46. expect { described_class.check(action: token.action, token: token.token) }
  47. .not_to change(described_class, :count)
  48. end
  49. end
  50. context 'at least one day after creation' do
  51. let(:created_at) { 1.day.ago }
  52. it 'returns nil' do
  53. expect(described_class.check(action: token.action, token: token.token)).to be_nil
  54. end
  55. it 'deletes the token' do
  56. token # create token
  57. expect { described_class.check(action: token.action, token: token.token) }
  58. .to change(described_class, :count).by(-1)
  59. end
  60. end
  61. end
  62. end
  63. describe 'permission matching' do
  64. subject(:token) { create(:api_token, user: agent, preferences: preferences) }
  65. let(:agent) { create(:agent) }
  66. let(:preferences) { { permission: %w[admin ticket.agent] } } # agent has no access to admin.*
  67. context 'with a permission shared by both token.user and token.preferences' do
  68. it 'returns token.user' do
  69. expect(described_class.check(action: token.action, token: token.token, permission: 'ticket.agent')).to eq(agent)
  70. end
  71. end
  72. context 'with the child of a permission shared by both token.user and token.preferences' do
  73. it 'returns token.user' do
  74. expect(described_class.check(action: token.action, token: token.token, permission: 'ticket.agent.foo')).to eq(agent)
  75. end
  76. end
  77. context 'with the parent of a permission shared by both token.user and token.preferences' do
  78. it 'returns nil' do
  79. expect(described_class.check(action: token.action, token: token.token, permission: 'ticket')).to be_nil
  80. end
  81. end
  82. context 'with a permission in token.preferences, but not on token.user' do
  83. it 'returns nil' do
  84. expect(described_class.check(action: token.action, token: token.token, permission: 'admin')).to be_nil
  85. end
  86. end
  87. context 'with a permission not in token.preferences, but on token.user' do
  88. it 'returns nil' do
  89. expect(described_class.check(action: token.action, token: token.token, permission: 'cti.agent')).to be_nil
  90. end
  91. end
  92. context 'with non-existent permission' do
  93. it 'returns nil' do
  94. expect(described_class.check(action: token.action, token: token.token, permission: 'foo')).to be_nil
  95. end
  96. end
  97. context 'with multiple permissions, where at least one is shared by both token.user and token.preferences' do
  98. it 'returns token.user' do
  99. expect(described_class.check(action: token.action, token: token.token, permission: %w[foo ticket.agent])).to eq(agent)
  100. end
  101. end
  102. end
  103. end
  104. describe '#fetch' do
  105. it 'returns nil when not present' do
  106. expect(described_class.fetch('token', user)).to be_nil
  107. end
  108. it 'returns token when present' do
  109. token
  110. expect(described_class.fetch(token.action, token.user.id)).to eq(token)
  111. end
  112. end
  113. describe '.ensure_token!' do
  114. it 'returns token when not present' do
  115. expect(described_class.ensure_token!('token', user.id)).to be_present
  116. end
  117. it 'returns token when present' do
  118. token
  119. expect(described_class.ensure_token!(token.action, token.user.id)).to eq(token.token)
  120. end
  121. describe 'with persistent argument' do
  122. it 'creates not-persistent token if argument omitted' do
  123. described_class.ensure_token!('token', user.id)
  124. expect(described_class.find_by(action: 'token')).not_to be_persistent
  125. end
  126. it 'creates persistent token when flag given' do
  127. described_class.ensure_token!('token', user.id, persistent: true)
  128. expect(described_class.find_by(action: 'token')).to be_persistent
  129. end
  130. end
  131. end
  132. describe '#renew_token!' do
  133. it 'changes token' do
  134. expect { token.renew_token! }.to change { token.reload.token }
  135. end
  136. end
  137. describe '.renew_token!' do
  138. it 'creates token when not present' do
  139. expect(described_class.renew_token!('token', user.id)).to be_present
  140. end
  141. it 'returns token when present' do
  142. token
  143. expect { described_class.renew_token!(token.action, token.user.id) }.to change { token.reload.token }
  144. end
  145. describe 'with persistent argument' do
  146. it 'creates not-persistent token if argument omitted' do
  147. described_class.renew_token!('token', user.id)
  148. expect(described_class.find_by(action: 'token')).not_to be_persistent
  149. end
  150. it 'creates persistent token when flag given' do
  151. described_class.renew_token!('token', user.id, persistent: true)
  152. expect(described_class.find_by(action: 'token')).to be_persistent
  153. end
  154. end
  155. end
  156. describe '#visible_in_frontend?' do
  157. it 'persistent api token is visible in frontend' do
  158. token = create(:token)
  159. expect(token).to be_visible_in_frontend
  160. end
  161. it 'persistent non-api token is not visible in frontend' do
  162. token = create(:token, action: :nonapi)
  163. expect(token).not_to be_visible_in_frontend
  164. end
  165. it 'non-persistent api token is not visible in frontend' do
  166. token = create(:token, persistent: false)
  167. expect(token).not_to be_visible_in_frontend
  168. end
  169. end
  170. describe '#trigger_user_subscription' do
  171. it 'triggers subscription when token is created' do
  172. allow(Gql::Subscriptions::User::Current::AccessTokenUpdates).to receive(:trigger)
  173. create(:token)
  174. expect(Gql::Subscriptions::User::Current::AccessTokenUpdates).to have_received(:trigger)
  175. end
  176. it 'triggers subscription when token is destroyed' do
  177. token = create(:token)
  178. allow(Gql::Subscriptions::User::Current::AccessTokenUpdates).to receive(:trigger)
  179. token.destroy
  180. expect(Gql::Subscriptions::User::Current::AccessTokenUpdates).to have_received(:trigger)
  181. end
  182. it 'does not trigger subscription when token is updated' do
  183. token = create(:token)
  184. allow(Gql::Subscriptions::User::Current::AccessTokenUpdates).to receive(:trigger)
  185. token.touch
  186. expect(Gql::Subscriptions::User::Current::AccessTokenUpdates).not_to have_received(:trigger)
  187. end
  188. it 'does not trigger subscription when non-api token is created' do
  189. allow(Gql::Subscriptions::User::Current::AccessTokenUpdates).to receive(:trigger)
  190. create(:token, action: :nonapi)
  191. expect(Gql::Subscriptions::User::Current::AccessTokenUpdates).not_to have_received(:trigger)
  192. end
  193. end
  194. describe '.cleanup' do
  195. context 'when token is non persistent and old' do
  196. let(:token) { create(:token, persistent: false, created_at: 1.year.ago) }
  197. it 'is removed' do
  198. expect { described_class.cleanup }
  199. .to change { described_class.exists? token.id }
  200. .to false
  201. end
  202. end
  203. context 'when token is non persistent and fresh' do
  204. let(:token) { create(:token, persistent: false, created_at: 1.day.ago) }
  205. it 'is not removed' do
  206. expect { described_class.cleanup }
  207. .not_to change { described_class.exists? token.id }
  208. .from true
  209. end
  210. end
  211. context 'when token is persistent and old' do
  212. let(:token) { create(:token, persistent: true, created_at: 1.day.ago) }
  213. it 'is not removed' do
  214. expect { described_class.cleanup }
  215. .not_to change { described_class.exists? token.id }
  216. .from true
  217. end
  218. end
  219. end
  220. end