# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

require 'rails_helper'

RSpec.describe 'Ticket zoom > Secure mailing', authenticated_as: :authenticate, type: :system do
  shared_examples 'secure mailing received mail' do
    context 'when receiving mail' do

      describe 'article meta information' do

        context 'when result is success' do
          let(:article) do
            create(:ticket_article, preferences: {
                     security: {
                       type:       'S/MIME',
                       encryption: {
                         success: true,
                         comment: 'COMMENT_ENCRYPT_SUCCESS',
                       },
                       sign:       {
                         success: true,
                         comment: 'COMMENT_SIGN_SUCCESS',
                       },
                     }
                   }, ticket: ticket)
          end

          it 'shows encryption/sign information' do

            visit "#ticket/zoom/#{article.ticket.id}"

            within :active_ticket_article, article do
              within '.alert--blank' do
                expect(page).to have_css('svg.icon-lock')
                  .and(have_css('svg.icon-signed'))
              end
            end

            open_article_meta

            within :active_ticket_article, article do
              within '.article-meta-value', text: %r{S/MIME} do
                expect(page).to have_css('span', text: 'Encrypted')
                  .and(have_css('span', text: 'Signed'))
                  .and(have_css('span[title=COMMENT_ENCRYPT_SUCCESS]'))
                  .and(have_css('span[title=COMMENT_SIGN_SUCCESS]'))
              end
            end
          end
        end

        context 'when result is sign block only' do
          let(:article) do
            create(:ticket_article, preferences: {
                     security: {
                       type: 'S/MIME',
                       sign: {
                         success: true,
                         comment: 'COMMENT_SIGN_SUCCESS',
                       },
                     }
                   }, ticket: ticket)
          end

          it 'shows sign but no encryption information' do
            visit "#ticket/zoom/#{article.ticket.id}"

            within :active_ticket_article, article do
              within '.alert--blank' do
                expect(page).to have_no_css('svg.icon-lock')
                  .and(have_css('svg.icon-signed'))
              end
            end

            open_article_meta

            within :active_ticket_article, article do
              within '.article-meta-value', text: %r{S/MIME} do
                expect(page).to have_no_css('span', text: 'Encrypted')
                  .and(have_no_css('span', text: 'Encryption error'))
                  .and(have_css('span', text: 'Signed'))
                  .and(have_css('span[title=COMMENT_SIGN_SUCCESS]'))
              end
            end
          end
        end

        context 'when result is encryption block and sign block without comment' do
          let(:article) do
            create(:ticket_article, preferences: {
                     security: {
                       type:       'S/MIME',
                       encryption: {
                         success: true,
                         comment: 'COMMENT_ENCRYPT_SUCCESS',
                       },
                       sign:       {
                         success: false
                       },
                     }
                   }, ticket: ticket)
          end

          it 'shows encryption but no sign information' do
            visit "#ticket/zoom/#{article.ticket.id}"

            within :active_ticket_article, article do
              within '.alert--blank' do
                expect(page).to have_css('svg.icon-lock')
                  .and(have_no_css('svg.icon-signed'))
              end
            end

            open_article_meta

            within :active_ticket_article, article do
              within '.article-meta-value', text: %r{S/MIME} do
                expect(page).to have_no_css('span', text: 'Signed')
                  .and(have_no_css('span', text: 'Sign error'))
                  .and(have_no_css('span', text: 'Encryption error'))
                  .and(have_css('span', text: 'Encrypted'))
                  .and(have_css('span[title=COMMENT_ENCRYPT_SUCCESS]'))
              end
            end
          end
        end

        context 'when result is error' do
          let(:article) do
            create(:ticket_article, preferences: {
                     security: {
                       type:       'S/MIME',
                       encryption: {
                         success: false,
                         comment: 'Encryption failed because XXX',
                       },
                       sign:       {
                         success: false,
                         comment: 'Sign failed because XXX',
                       },
                     }
                   }, ticket: ticket)
          end

          it 'shows create information about encryption/sign failed' do
            visit "#ticket/zoom/#{article.ticket.id}"

            within :active_ticket_article, article do
              expect(page).to have_css('svg.icon-not-signed')

              expect(page).to have_css('div.alert.alert--warning', text: 'Encryption failed because XXX')
                .and(have_css('div.alert.alert--warning', text: 'Sign failed because XXX'))
            end

            open_article_meta

            within :active_ticket_article, article do
              within '.article-meta-value', text: %r{S/MIME} do
                expect(page).to have_css('span', text: 'Encryption error')
                  .and(have_css('span', text: 'Sign error'))
                  .and(have_css('span[title="Encryption failed because XXX"]'))
                  .and(have_css('span[title="Sign failed because XXX"]'))
              end
            end
          end
        end
      end

      context 'when certificate not present at time of arrival' do
        let(:mail) do
          secure_mailing_1 = create(secure_mailing_factory_name, :with_private, fixture: system_email_address)
          secure_mailing_2 = create(secure_mailing_factory_name, :with_private, fixture: sender_email_address)

          mail = Channel::EmailBuild.build(
            from:         sender_email_address,
            to:           system_email_address,
            body:         'somebody with some text',
            content_type: 'text/plain',
            security:     {
              type:       secure_mailing_type_name,
              sign:       {
                success: true,
              },
              encryption: {
                success: true,
              },
            },
          )

          secure_mailing_1.destroy
          secure_mailing_2.destroy

          mail
        end

        it 'does retry successfully' do
          parsed_mail = Channel::EmailParser.new.parse(mail.to_s)
          ticket, article, _user, _mail = Channel::EmailParser.new.process({ group_id: group.id }, parsed_mail['raw'])
          expect(Ticket::Article.find(article.id).body).to eq('no visible content')

          create(secure_mailing_factory_name, fixture: sender_email_address)
          create(secure_mailing_factory_name, :with_private, fixture: system_email_address)

          visit "#ticket/zoom/#{ticket.id}"
          expect(page).to have_no_css('.article-content', text: 'somebody with some text')
          click '.js-securityRetryProcess'
          expect(page).to have_css('.article-content', text: 'somebody with some text')
        end

        it 'does fail on retry (S/MIME function buttons no longer working in tickets #3957)' do
          parsed_mail = Channel::EmailParser.new.parse(mail.to_s)
          ticket, article, _user, _mail = Channel::EmailParser.new.process({ group_id: group.id }, parsed_mail['raw'])
          expect(Ticket::Article.find(article.id).body).to eq('no visible content')

          visit "#ticket/zoom/#{ticket.id}"
          expect(page).to have_no_css('.article-content', text: 'somebody with some text')
          click '.js-securityRetryProcess'
          expect(page).to have_css('#notify', text: secure_mailing_decryption_failed_message)
        end
      end
    end
  end

  shared_examples 'secure mailing replying' do
    context 'when replying', authenticated_as: :setup_and_authenticate do

      def setup_and_authenticate
        create(:ticket_article, ticket: ticket, from: customer.email)

        create(secure_mailing_factory_name, :with_private, fixture: system_email_address)
        create(secure_mailing_factory_name, fixture: sender_email_address)

        authenticate
      end

      it 'plain' do
        visit "#ticket/zoom/#{ticket.id}"

        all('a[data-type=emailReply]').last.click
        find('.articleNewEdit-body').send_keys('Test')

        expect(page).to have_css('.js-securityEncrypt.btn--active')
          .and(have_css('.js-securitySign.btn--active'))

        click '.js-securityEncrypt'
        click '.js-securitySign'

        click '.js-submit'
        expect(page).to have_css('.ticket-article-item', count: 2)

        expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be_nil
        expect(Ticket::Article.last.preferences['security']['sign']['success']).to be_nil
      end

      it 'signed' do
        visit "#ticket/zoom/#{ticket.id}"

        all('a[data-type=emailReply]').last.click
        find('.articleNewEdit-body').send_keys('Test')

        expect(page).to have_css('.js-securityEncrypt.btn--active')
        expect(page).to have_css('.js-securitySign.btn--active')

        click '.js-securityEncrypt'

        click '.js-submit'
        expect(page).to have_css('.ticket-article-item', count: 2)

        expect(Ticket::Article.last.preferences).to include(
          'security' => include(
            'encryption' => be_empty,
            'sign'       => include('success' => be_truthy),
          )
        )
      end

      it 'encrypted' do
        visit "#ticket/zoom/#{ticket.id}"

        all('a[data-type=emailReply]').last.click
        find('.articleNewEdit-body').send_keys('Test')

        expect(page).to have_css('.js-securityEncrypt.btn--active')
        expect(page).to have_css('.js-securitySign.btn--active')

        click '.js-securitySign'

        click '.js-submit'
        expect(page).to have_css('.ticket-article-item', count: 2)

        expect(Ticket::Article.last.preferences).to include(
          'security' => include(
            'encryption' => include('success' => be_truthy),
            'sign'       => be_empty,
          )
        )
      end

      it 'signed and encrypted' do
        visit "#ticket/zoom/#{ticket.id}"

        all('a[data-type=emailReply]').last.click
        find('.articleNewEdit-body').send_keys('Test')

        expect(page).to have_css('.js-securityEncrypt.btn--active')
        expect(page).to have_css('.js-securitySign.btn--active')

        click '.js-submit'
        expect(page).to have_css('.ticket-article-item', count: 2)

        expect(Ticket::Article.last.preferences).to include(
          'security' => include(
            'encryption' => include('success' => be_truthy),
            'sign'       => include('success' => be_truthy),
          )
        )
      end
    end
  end

  shared_examples 'secure mailing group behavior' do
    describe 'Group default behavior', authenticated_as: :setup_and_authenticate do

      let(:secure_mailing_config) { {} }

      def setup_and_authenticate
        Setting.set(secure_mailing_config_name, secure_mailing_config)

        create(:ticket_article, ticket: ticket, from: customer.email)

        create(secure_mailing_factory_name, :with_private, fixture: system_email_address)
        create(secure_mailing_factory_name, fixture: sender_email_address)

        authenticate
      end

      shared_examples 'security defaults example' do |sign:, encrypt:|

        it "security defaults sign: #{sign}, encrypt: #{encrypt}" do
          within(:active_content) do
            if sign
              expect(page).to have_css('.js-securitySign.btn--active')
            else
              expect(page).to have_no_css('.js-securitySign.btn--active')
            end
            if encrypt
              expect(page).to have_css('.js-securityEncrypt.btn--active')
            else
              expect(page).to have_no_css('.js-securityEncrypt.btn--active')
            end
          end
        end
      end

      shared_examples 'security defaults' do |sign:, encrypt:|

        before do
          visit "#ticket/zoom/#{ticket.id}"

          within(:active_content) do
            all('a[data-type=emailReply]').last.click
            find('.articleNewEdit-body').send_keys('Test')
          end
        end

        include_examples 'security defaults example', sign: sign, encrypt: encrypt
      end

      shared_examples 'security defaults group change' do |sign:, encrypt:|

        before do
          visit "#ticket/zoom/#{ticket.id}"

          within(:active_content) do
            all('a[data-type=emailReply]').last.click
            find('.articleNewEdit-body').send_keys('Test')

            set_tree_select_value('group_id', new_group.name)
          end
        end

        include_examples 'security defaults example', sign: sign, encrypt: encrypt
      end

      context 'when not configured' do
        it_behaves_like 'security defaults', sign: true, encrypt: true
      end

      context 'when configuration present' do

        let(:secure_mailing_config) do
          {
            'group_id' => group_defaults
          }
        end

        let(:group_defaults) do
          {
            'default_encryption' => {
              group.id.to_s => default_encryption,
            },
            'default_sign'       => {
              group.id.to_s => default_sign,
            }
          }
        end

        let(:default_sign)       { true }
        let(:default_encryption) { true }

        shared_examples 'sign and encrypt variations' do |check_examples_name|

          it_behaves_like check_examples_name, sign: true, encrypt: true

          context 'when no value present' do
            let(:group_defaults) { {} }

            it_behaves_like check_examples_name, sign: true, encrypt: true
          end

          context 'when signing is disabled' do
            let(:default_sign) { false }

            it_behaves_like check_examples_name, sign: false, encrypt: true
          end

          context 'when encryption is disabled' do
            let(:default_encryption) { false }

            it_behaves_like check_examples_name, sign: true, encrypt: false
          end
        end

        context 'when same Group' do
          it_behaves_like 'sign and encrypt variations', 'security defaults'
        end

        context 'when Group change' do
          let(:new_group) { create(:group, email_address: email_address) }

          let(:agent_groups) { [group, new_group] }

          let(:group_defaults) do
            {
              'default_encryption' => {
                new_group.id.to_s => default_encryption,
              },
              'default_sign'       => {
                new_group.id.to_s => default_sign,
              }
            }
          end

          it_behaves_like 'sign and encrypt variations', 'security defaults group change'
        end
      end
    end
  end

  describe 'S/MIME active', authenticated_as: :authenticate do
    let(:system_email_address) { 'smime1@example.com' }
    let(:email_address)        { create(:email_address, email: system_email_address) }
    let(:group)                { create(:group, email_address: email_address) }
    let(:agent_groups)         { [group] }
    let(:agent)                { create(:agent, groups: agent_groups) }

    let(:secure_mailing_factory_name) { :smime_certificate }
    let(:secure_mailing_config_name)               { :smime_config }
    let(:secure_mailing_type_name)                 { 'S/MIME' }
    let(:secure_mailing_decryption_failed_message) { 'Decryption failed! The private key for decryption could not be found.' }

    let(:sender_email_address) { 'smime2@example.com' }
    let(:customer) { create(:customer, email: sender_email_address) }

    let(:ticket) { create(:ticket, group: group, owner: agent, customer: customer) }

    def authenticate
      Setting.set('smime_integration', true)
      agent
    end

    include_examples 'secure mailing received mail'
    include_examples 'secure mailing replying'
    include_examples 'secure mailing group behavior'
  end

  describe 'PGP active', authenticated_as: :authenticate do
    let(:system_email_address) { 'pgp1@example.com' }
    let(:email_address)        { create(:email_address, email: system_email_address) }
    let(:group)                { create(:group, email_address: email_address) }
    let(:agent_groups)         { [group] }
    let(:agent)                { create(:agent, groups: agent_groups) }

    let(:secure_mailing_factory_name) { :pgp_key }
    let(:secure_mailing_config_name)               { :pgp_config }
    let(:secure_mailing_type_name)                 { 'PGP' }
    let(:secure_mailing_decryption_failed_message) { 'Decryption failed! The private PGP key could not be found.' }

    let(:sender_email_address) { 'pgp2@example.com' }
    let(:customer) { create(:customer, email: sender_email_address) }

    let(:ticket) { create(:ticket, group: group, owner: agent, customer: customer) }

    def authenticate
      Setting.set('pgp_integration', true)
      agent
    end

    include_examples 'secure mailing received mail'
    include_examples 'secure mailing replying'
    include_examples 'secure mailing group behavior'
  end

  context 'with both PGP and S/MIME integration', authenticated_as: :authenticate do
    def authenticate
      Setting.set('pgp_integration', true)
      Setting.set('smime_integration', true)

      agent
    end

    let(:system_email_address)    { 'pgp+smime-sender@example.com' }
    let(:recipient_email_address) { 'pgp+smime-recipient@example.com' }

    let(:email_address) { create(:email_address, email: system_email_address) }
    let(:group)         { create(:group, email_address: email_address) }
    let(:agent)         { create(:agent, groups: [group]) }
    let(:customer)      { create(:customer, email: recipient_email_address) }
    let(:ticket)        { create(:ticket, group: group, owner: agent, customer: customer) }

    shared_examples 'showing security type switcher' do
      it 'shows security type switcher' do
        visit "#ticket/zoom/#{ticket.id}"

        within(:active_content) do
          all('a[data-type=emailReply]').last.click

          expect(page).to have_css('.btn', text: 'PGP')
            .and(have_css('.btn.btn--active', text: 'S/MIME')) # preferred
        end
      end
    end

    context 'with no certificates nor keys present' do
      before do
        create(:ticket_article, ticket: ticket, from: customer.email)
      end

      it_behaves_like 'showing security type switcher'
    end

    context 'with certificates and keys present' do
      before do
        create(:ticket_article, ticket: ticket, from: customer.email)

        create(:pgp_key, :with_private, fixture: system_email_address)
        create(:pgp_key, fixture: recipient_email_address)
        create(:smime_certificate, :with_private, fixture: system_email_address)
        create(:smime_certificate, fixture: recipient_email_address)
      end

      it_behaves_like 'showing security type switcher'

      it 'switches between security types' do
        visit "#ticket/zoom/#{ticket.id}"

        within(:active_content) do
          all('a[data-type=emailReply]').last.click

          # Wait until the security options check AJAX call is ready.
          expect(page).to have_css('div.js-securityEncrypt.btn--active')
            .and(have_css('div.js-securitySign.btn--active'))

          expect(page).to have_css('.btn', text: 'PGP')
            .and(have_css('.btn.btn--active', text: 'S/MIME')) # preferred

          expect(find('.js-securityEncryptComment')['title']).to eq('The certificates for pgp+smime-recipient@example.com were found.')
          expect(find('.js-securitySignComment')['title']).to eq('The certificate for pgp+smime-sender@example.com was found.')

          click '.btn', text: 'PGP'

          # Wait until the security options check AJAX call is ready.
          expect(page).to have_css('div.js-securityEncrypt.btn--active')
            .and(have_css('div.js-securitySign.btn--active'))

          expect(page).to have_no_css('.btn.btn--active', text: 'S/MIME')
            .and(have_css('.btn.btn--active', text: 'PGP'))

          expect(find('.js-securityEncryptComment')['title']).to eq('The PGP keys for pgp+smime-recipient@example.com were found.')
          expect(find('.js-securitySignComment')['title']).to eq('The PGP key for pgp+smime-sender@example.com was found.')
        end
      end
    end
  end
end