secure_mailing_spec.rb 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe 'Ticket zoom > Secure mailing', authenticated_as: :authenticate, type: :system do
  4. shared_examples 'secure mailing received mail' do
  5. context 'when receiving mail' do
  6. describe 'article meta information' do
  7. context 'when result is success' do
  8. let(:article) do
  9. create(:ticket_article, preferences: {
  10. security: {
  11. type: 'S/MIME',
  12. encryption: {
  13. success: true,
  14. comment: 'COMMENT_ENCRYPT_SUCCESS',
  15. },
  16. sign: {
  17. success: true,
  18. comment: 'COMMENT_SIGN_SUCCESS',
  19. },
  20. }
  21. }, ticket: ticket)
  22. end
  23. it 'shows encryption/sign information' do
  24. visit "#ticket/zoom/#{article.ticket.id}"
  25. within :active_ticket_article, article do
  26. within '.alert--blank' do
  27. expect(page).to have_css('svg.icon-lock')
  28. .and(have_css('svg.icon-signed'))
  29. end
  30. end
  31. open_article_meta
  32. within :active_ticket_article, article do
  33. within '.article-meta-value', text: %r{S/MIME} do
  34. expect(page).to have_css('span', text: 'Encrypted')
  35. .and(have_css('span', text: 'Signed'))
  36. .and(have_css('span[title=COMMENT_ENCRYPT_SUCCESS]'))
  37. .and(have_css('span[title=COMMENT_SIGN_SUCCESS]'))
  38. end
  39. end
  40. end
  41. end
  42. context 'when result is sign block only' do
  43. let(:article) do
  44. create(:ticket_article, preferences: {
  45. security: {
  46. type: 'S/MIME',
  47. sign: {
  48. success: true,
  49. comment: 'COMMENT_SIGN_SUCCESS',
  50. },
  51. }
  52. }, ticket: ticket)
  53. end
  54. it 'shows sign but no encryption information' do
  55. visit "#ticket/zoom/#{article.ticket.id}"
  56. within :active_ticket_article, article do
  57. within '.alert--blank' do
  58. expect(page).to have_no_css('svg.icon-lock')
  59. .and(have_css('svg.icon-signed'))
  60. end
  61. end
  62. open_article_meta
  63. within :active_ticket_article, article do
  64. within '.article-meta-value', text: %r{S/MIME} do
  65. expect(page).to have_no_css('span', text: 'Encrypted')
  66. .and(have_no_css('span', text: 'Encryption error'))
  67. .and(have_css('span', text: 'Signed'))
  68. .and(have_css('span[title=COMMENT_SIGN_SUCCESS]'))
  69. end
  70. end
  71. end
  72. end
  73. context 'when result is encryption block and sign block without comment' do
  74. let(:article) do
  75. create(:ticket_article, preferences: {
  76. security: {
  77. type: 'S/MIME',
  78. encryption: {
  79. success: true,
  80. comment: 'COMMENT_ENCRYPT_SUCCESS',
  81. },
  82. sign: {
  83. success: false
  84. },
  85. }
  86. }, ticket: ticket)
  87. end
  88. it 'shows encryption but no sign information' do
  89. visit "#ticket/zoom/#{article.ticket.id}"
  90. within :active_ticket_article, article do
  91. within '.alert--blank' do
  92. expect(page).to have_css('svg.icon-lock')
  93. .and(have_no_css('svg.icon-signed'))
  94. end
  95. end
  96. open_article_meta
  97. within :active_ticket_article, article do
  98. within '.article-meta-value', text: %r{S/MIME} do
  99. expect(page).to have_no_css('span', text: 'Signed')
  100. .and(have_no_css('span', text: 'Sign error'))
  101. .and(have_no_css('span', text: 'Encryption error'))
  102. .and(have_css('span', text: 'Encrypted'))
  103. .and(have_css('span[title=COMMENT_ENCRYPT_SUCCESS]'))
  104. end
  105. end
  106. end
  107. end
  108. context 'when result is error' do
  109. let(:article) do
  110. create(:ticket_article, preferences: {
  111. security: {
  112. type: 'S/MIME',
  113. encryption: {
  114. success: false,
  115. comment: 'Encryption failed because XXX',
  116. },
  117. sign: {
  118. success: false,
  119. comment: 'Sign failed because XXX',
  120. },
  121. }
  122. }, ticket: ticket)
  123. end
  124. it 'shows create information about encryption/sign failed' do
  125. visit "#ticket/zoom/#{article.ticket.id}"
  126. within :active_ticket_article, article do
  127. expect(page).to have_css('svg.icon-not-signed')
  128. expect(page).to have_css('div.alert.alert--warning', text: 'Encryption failed because XXX')
  129. .and(have_css('div.alert.alert--warning', text: 'Sign failed because XXX'))
  130. end
  131. open_article_meta
  132. within :active_ticket_article, article do
  133. within '.article-meta-value', text: %r{S/MIME} do
  134. expect(page).to have_css('span', text: 'Encryption error')
  135. .and(have_css('span', text: 'Sign error'))
  136. .and(have_css('span[title="Encryption failed because XXX"]'))
  137. .and(have_css('span[title="Sign failed because XXX"]'))
  138. end
  139. end
  140. end
  141. end
  142. end
  143. context 'when certificate not present at time of arrival' do
  144. let(:mail) do
  145. secure_mailing_1 = create(secure_mailing_factory_name, :with_private, fixture: system_email_address)
  146. secure_mailing_2 = create(secure_mailing_factory_name, :with_private, fixture: sender_email_address)
  147. mail = Channel::EmailBuild.build(
  148. from: sender_email_address,
  149. to: system_email_address,
  150. body: 'somebody with some text',
  151. content_type: 'text/plain',
  152. security: {
  153. type: secure_mailing_type_name,
  154. sign: {
  155. success: true,
  156. },
  157. encryption: {
  158. success: true,
  159. },
  160. },
  161. )
  162. secure_mailing_1.destroy
  163. secure_mailing_2.destroy
  164. mail
  165. end
  166. it 'does retry successfully' do
  167. parsed_mail = Channel::EmailParser.new.parse(mail.to_s)
  168. ticket, article, _user, _mail = Channel::EmailParser.new.process({ group_id: group.id }, parsed_mail['raw'])
  169. expect(Ticket::Article.find(article.id).body).to eq('no visible content')
  170. create(secure_mailing_factory_name, fixture: sender_email_address)
  171. create(secure_mailing_factory_name, :with_private, fixture: system_email_address)
  172. visit "#ticket/zoom/#{ticket.id}"
  173. expect(page).to have_no_css('.article-content', text: 'somebody with some text')
  174. click '.js-securityRetryProcess'
  175. expect(page).to have_css('.article-content', text: 'somebody with some text')
  176. end
  177. it 'does fail on retry (S/MIME function buttons no longer working in tickets #3957)' do
  178. parsed_mail = Channel::EmailParser.new.parse(mail.to_s)
  179. ticket, article, _user, _mail = Channel::EmailParser.new.process({ group_id: group.id }, parsed_mail['raw'])
  180. expect(Ticket::Article.find(article.id).body).to eq('no visible content')
  181. visit "#ticket/zoom/#{ticket.id}"
  182. expect(page).to have_no_css('.article-content', text: 'somebody with some text')
  183. click '.js-securityRetryProcess'
  184. expect(page).to have_css('#notify', text: secure_mailing_decryption_failed_message)
  185. end
  186. end
  187. end
  188. end
  189. shared_examples 'secure mailing replying' do
  190. context 'when replying', authenticated_as: :setup_and_authenticate do
  191. def setup_and_authenticate
  192. create(:ticket_article, ticket: ticket, from: customer.email)
  193. create(secure_mailing_factory_name, :with_private, fixture: system_email_address)
  194. create(secure_mailing_factory_name, fixture: sender_email_address)
  195. authenticate
  196. end
  197. it 'plain' do
  198. visit "#ticket/zoom/#{ticket.id}"
  199. all('a[data-type=emailReply]').last.click
  200. find('.articleNewEdit-body').send_keys('Test')
  201. expect(page).to have_css('.js-securityEncrypt.btn--active')
  202. .and(have_css('.js-securitySign.btn--active'))
  203. click '.js-securityEncrypt'
  204. click '.js-securitySign'
  205. click '.js-submit'
  206. expect(page).to have_css('.ticket-article-item', count: 2)
  207. expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be_nil
  208. expect(Ticket::Article.last.preferences['security']['sign']['success']).to be_nil
  209. end
  210. it 'signed' do
  211. visit "#ticket/zoom/#{ticket.id}"
  212. all('a[data-type=emailReply]').last.click
  213. find('.articleNewEdit-body').send_keys('Test')
  214. expect(page).to have_css('.js-securityEncrypt.btn--active')
  215. expect(page).to have_css('.js-securitySign.btn--active')
  216. click '.js-securityEncrypt'
  217. click '.js-submit'
  218. expect(page).to have_css('.ticket-article-item', count: 2)
  219. expect(Ticket::Article.last.preferences).to include(
  220. 'security' => include(
  221. 'encryption' => be_empty,
  222. 'sign' => include('success' => be_truthy),
  223. )
  224. )
  225. end
  226. it 'encrypted' do
  227. visit "#ticket/zoom/#{ticket.id}"
  228. all('a[data-type=emailReply]').last.click
  229. find('.articleNewEdit-body').send_keys('Test')
  230. expect(page).to have_css('.js-securityEncrypt.btn--active')
  231. expect(page).to have_css('.js-securitySign.btn--active')
  232. click '.js-securitySign'
  233. click '.js-submit'
  234. expect(page).to have_css('.ticket-article-item', count: 2)
  235. expect(Ticket::Article.last.preferences).to include(
  236. 'security' => include(
  237. 'encryption' => include('success' => be_truthy),
  238. 'sign' => be_empty,
  239. )
  240. )
  241. end
  242. it 'signed and encrypted' do
  243. visit "#ticket/zoom/#{ticket.id}"
  244. all('a[data-type=emailReply]').last.click
  245. find('.articleNewEdit-body').send_keys('Test')
  246. expect(page).to have_css('.js-securityEncrypt.btn--active')
  247. expect(page).to have_css('.js-securitySign.btn--active')
  248. click '.js-submit'
  249. expect(page).to have_css('.ticket-article-item', count: 2)
  250. expect(Ticket::Article.last.preferences).to include(
  251. 'security' => include(
  252. 'encryption' => include('success' => be_truthy),
  253. 'sign' => include('success' => be_truthy),
  254. )
  255. )
  256. end
  257. end
  258. end
  259. shared_examples 'secure mailing group behavior' do
  260. describe 'Group default behavior', authenticated_as: :setup_and_authenticate do
  261. let(:secure_mailing_config) { {} }
  262. def setup_and_authenticate
  263. Setting.set(secure_mailing_config_name, secure_mailing_config)
  264. create(:ticket_article, ticket: ticket, from: customer.email)
  265. create(secure_mailing_factory_name, :with_private, fixture: system_email_address)
  266. create(secure_mailing_factory_name, fixture: sender_email_address)
  267. authenticate
  268. end
  269. shared_examples 'security defaults example' do |sign:, encrypt:|
  270. it "security defaults sign: #{sign}, encrypt: #{encrypt}" do
  271. within(:active_content) do
  272. if sign
  273. expect(page).to have_css('.js-securitySign.btn--active')
  274. else
  275. expect(page).to have_no_css('.js-securitySign.btn--active')
  276. end
  277. if encrypt
  278. expect(page).to have_css('.js-securityEncrypt.btn--active')
  279. else
  280. expect(page).to have_no_css('.js-securityEncrypt.btn--active')
  281. end
  282. end
  283. end
  284. end
  285. shared_examples 'security defaults' do |sign:, encrypt:|
  286. before do
  287. visit "#ticket/zoom/#{ticket.id}"
  288. within(:active_content) do
  289. all('a[data-type=emailReply]').last.click
  290. find('.articleNewEdit-body').send_keys('Test')
  291. end
  292. end
  293. include_examples 'security defaults example', sign: sign, encrypt: encrypt
  294. end
  295. shared_examples 'security defaults group change' do |sign:, encrypt:|
  296. before do
  297. visit "#ticket/zoom/#{ticket.id}"
  298. within(:active_content) do
  299. all('a[data-type=emailReply]').last.click
  300. find('.articleNewEdit-body').send_keys('Test')
  301. set_tree_select_value('group_id', new_group.name)
  302. end
  303. end
  304. include_examples 'security defaults example', sign: sign, encrypt: encrypt
  305. end
  306. context 'when not configured' do
  307. it_behaves_like 'security defaults', sign: true, encrypt: true
  308. end
  309. context 'when configuration present' do
  310. let(:secure_mailing_config) do
  311. {
  312. 'group_id' => group_defaults
  313. }
  314. end
  315. let(:group_defaults) do
  316. {
  317. 'default_encryption' => {
  318. group.id.to_s => default_encryption,
  319. },
  320. 'default_sign' => {
  321. group.id.to_s => default_sign,
  322. }
  323. }
  324. end
  325. let(:default_sign) { true }
  326. let(:default_encryption) { true }
  327. shared_examples 'sign and encrypt variations' do |check_examples_name|
  328. it_behaves_like check_examples_name, sign: true, encrypt: true
  329. context 'when no value present' do
  330. let(:group_defaults) { {} }
  331. it_behaves_like check_examples_name, sign: true, encrypt: true
  332. end
  333. context 'when signing is disabled' do
  334. let(:default_sign) { false }
  335. it_behaves_like check_examples_name, sign: false, encrypt: true
  336. end
  337. context 'when encryption is disabled' do
  338. let(:default_encryption) { false }
  339. it_behaves_like check_examples_name, sign: true, encrypt: false
  340. end
  341. end
  342. context 'when same Group' do
  343. it_behaves_like 'sign and encrypt variations', 'security defaults'
  344. end
  345. context 'when Group change' do
  346. let(:new_group) { create(:group, email_address: email_address) }
  347. let(:agent_groups) { [group, new_group] }
  348. let(:group_defaults) do
  349. {
  350. 'default_encryption' => {
  351. new_group.id.to_s => default_encryption,
  352. },
  353. 'default_sign' => {
  354. new_group.id.to_s => default_sign,
  355. }
  356. }
  357. end
  358. it_behaves_like 'sign and encrypt variations', 'security defaults group change'
  359. end
  360. end
  361. end
  362. end
  363. describe 'S/MIME active', authenticated_as: :authenticate do
  364. let(:system_email_address) { 'smime1@example.com' }
  365. let(:email_address) { create(:email_address, email: system_email_address) }
  366. let(:group) { create(:group, email_address: email_address) }
  367. let(:agent_groups) { [group] }
  368. let(:agent) { create(:agent, groups: agent_groups) }
  369. let(:secure_mailing_factory_name) { :smime_certificate }
  370. let(:secure_mailing_config_name) { :smime_config }
  371. let(:secure_mailing_type_name) { 'S/MIME' }
  372. let(:secure_mailing_decryption_failed_message) { 'Decryption failed! The private key for decryption could not be found.' }
  373. let(:sender_email_address) { 'smime2@example.com' }
  374. let(:customer) { create(:customer, email: sender_email_address) }
  375. let(:ticket) { create(:ticket, group: group, owner: agent, customer: customer) }
  376. def authenticate
  377. Setting.set('smime_integration', true)
  378. agent
  379. end
  380. include_examples 'secure mailing received mail'
  381. include_examples 'secure mailing replying'
  382. include_examples 'secure mailing group behavior'
  383. end
  384. describe 'PGP active', authenticated_as: :authenticate do
  385. let(:system_email_address) { 'pgp1@example.com' }
  386. let(:email_address) { create(:email_address, email: system_email_address) }
  387. let(:group) { create(:group, email_address: email_address) }
  388. let(:agent_groups) { [group] }
  389. let(:agent) { create(:agent, groups: agent_groups) }
  390. let(:secure_mailing_factory_name) { :pgp_key }
  391. let(:secure_mailing_config_name) { :pgp_config }
  392. let(:secure_mailing_type_name) { 'PGP' }
  393. let(:secure_mailing_decryption_failed_message) { 'Decryption failed! The private PGP key could not be found.' }
  394. let(:sender_email_address) { 'pgp2@example.com' }
  395. let(:customer) { create(:customer, email: sender_email_address) }
  396. let(:ticket) { create(:ticket, group: group, owner: agent, customer: customer) }
  397. def authenticate
  398. Setting.set('pgp_integration', true)
  399. agent
  400. end
  401. include_examples 'secure mailing received mail'
  402. include_examples 'secure mailing replying'
  403. include_examples 'secure mailing group behavior'
  404. end
  405. context 'with both PGP and S/MIME integration', authenticated_as: :authenticate do
  406. def authenticate
  407. Setting.set('pgp_integration', true)
  408. Setting.set('smime_integration', true)
  409. agent
  410. end
  411. let(:system_email_address) { 'pgp+smime-sender@example.com' }
  412. let(:recipient_email_address) { 'pgp+smime-recipient@example.com' }
  413. let(:email_address) { create(:email_address, email: system_email_address) }
  414. let(:group) { create(:group, email_address: email_address) }
  415. let(:agent) { create(:agent, groups: [group]) }
  416. let(:customer) { create(:customer, email: recipient_email_address) }
  417. let(:ticket) { create(:ticket, group: group, owner: agent, customer: customer) }
  418. shared_examples 'showing security type switcher' do
  419. it 'shows security type switcher' do
  420. visit "#ticket/zoom/#{ticket.id}"
  421. within(:active_content) do
  422. all('a[data-type=emailReply]').last.click
  423. expect(page).to have_css('.btn', text: 'PGP')
  424. .and(have_css('.btn.btn--active', text: 'S/MIME')) # preferred
  425. end
  426. end
  427. end
  428. context 'with no certificates nor keys present' do
  429. before do
  430. create(:ticket_article, ticket: ticket, from: customer.email)
  431. end
  432. it_behaves_like 'showing security type switcher'
  433. end
  434. context 'with certificates and keys present' do
  435. before do
  436. create(:ticket_article, ticket: ticket, from: customer.email)
  437. create(:pgp_key, :with_private, fixture: system_email_address)
  438. create(:pgp_key, fixture: recipient_email_address)
  439. create(:smime_certificate, :with_private, fixture: system_email_address)
  440. create(:smime_certificate, fixture: recipient_email_address)
  441. end
  442. it_behaves_like 'showing security type switcher'
  443. it 'switches between security types' do
  444. visit "#ticket/zoom/#{ticket.id}"
  445. within(:active_content) do
  446. all('a[data-type=emailReply]').last.click
  447. # Wait until the security options check AJAX call is ready.
  448. expect(page).to have_css('div.js-securityEncrypt.btn--active')
  449. .and(have_css('div.js-securitySign.btn--active'))
  450. expect(page).to have_css('.btn', text: 'PGP')
  451. .and(have_css('.btn.btn--active', text: 'S/MIME')) # preferred
  452. expect(find('.js-securityEncryptComment')['title']).to eq('The certificates for pgp+smime-recipient@example.com were found.')
  453. expect(find('.js-securitySignComment')['title']).to eq('The certificate for pgp+smime-sender@example.com was found.')
  454. click '.btn', text: 'PGP'
  455. # Wait until the security options check AJAX call is ready.
  456. expect(page).to have_css('div.js-securityEncrypt.btn--active')
  457. .and(have_css('div.js-securitySign.btn--active'))
  458. expect(page).to have_no_css('.btn.btn--active', text: 'S/MIME')
  459. .and(have_css('.btn.btn--active', text: 'PGP'))
  460. expect(find('.js-securityEncryptComment')['title']).to eq('The PGP keys for pgp+smime-recipient@example.com were found.')
  461. expect(find('.js-securitySignComment')['title']).to eq('The PGP key for pgp+smime-sender@example.com was found.')
  462. end
  463. end
  464. end
  465. end
  466. end