From afa6b9984a326b772fe6a47356c04f639e06bc7c Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Fri, 17 Apr 2020 09:24:57 +0200 Subject: [PATCH] Fixes #3022 - Detect ServiceNow follow-ups --- .../channel/filter/service_now_check.rb | 60 +++++++++++++++++++ .../20200401000001_service_now_config.rb | 28 +++++++++ db/seeds/settings.rb | 18 ++++++ spec/models/channel/email_parser_spec.rb | 28 +++++++++ test/data/mail/mail089.box | 22 +++++++ test/data/mail/mail090.box | 22 +++++++ 6 files changed, 178 insertions(+) create mode 100644 app/models/channel/filter/service_now_check.rb create mode 100644 db/migrate/20200401000001_service_now_config.rb create mode 100644 test/data/mail/mail089.box create mode 100644 test/data/mail/mail090.box diff --git a/app/models/channel/filter/service_now_check.rb b/app/models/channel/filter/service_now_check.rb new file mode 100644 index 000000000..45f188d43 --- /dev/null +++ b/app/models/channel/filter/service_now_check.rb @@ -0,0 +1,60 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +module Channel::Filter::ServiceNowCheck + + # This filter will run pre and post + def self.run(_channel, mail, ticket = nil, _article = nil, _session_user = nil) + source_id = self.source_id(from: mail[:from], subject: mail[:subject]) + return if source_id.blank? + + # check if we can followup by existing service now relation + if ticket.blank? + sync_entry = ExternalSync.find_by( + source: 'ServiceNow', + source_id: source_id, + object: 'Ticket', + ) + return if sync_entry.blank? + + mail[ 'x-zammad-ticket-id'.to_sym ] = sync_entry.o_id + return + end + + ExternalSync.create_with(source_id: source_id).find_or_create_by(source: 'ServiceNow', object: 'Ticket', o_id: ticket.id) + end + +=begin + +This function returns the source id of the service now email if given. + + source_id = Channel::Filter::ServiceNowCheck.source_id( + from: 'test@servicnow.com', + subject: 'Incident INC12345 --- test', + ) + +returns: + + source_id = 'INC12345' + +=end + + def self.source_id(from: '', subject: '') + + # check if data is sent by service now + begin + return if Mail::AddressList.new(from).addresses.none? do |line| + line.address.end_with?('@service-now.com') + end + rescue + Rails.logger.info "Unable to parse email address in '#{from}'" + end + + # check if we can find the service now relation + source_id = nil + if subject =~ /\s(INC\d+)\s/ + source_id = $1 + end + + source_id + end +end diff --git a/db/migrate/20200401000001_service_now_config.rb b/db/migrate/20200401000001_service_now_config.rb new file mode 100644 index 000000000..4a120dbde --- /dev/null +++ b/db/migrate/20200401000001_service_now_config.rb @@ -0,0 +1,28 @@ +class ServiceNowConfig < ActiveRecord::Migration[4.2] + def up + + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '5400_postmaster_filter_service_now_check', + area: 'Postmaster::PreFilter', + description: 'Defines postmaster filter to identify service now mails for correct follow-ups.', + options: {}, + state: 'Channel::Filter::ServiceNowCheck', + frontend: false + ) + + Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '5401_postmaster_filter_service_now_check', + area: 'Postmaster::PostFilter', + description: 'Defines postmaster filter to identify service now mails for correct follow-ups.', + options: {}, + state: 'Channel::Filter::ServiceNowCheck', + frontend: false + ) + end + +end diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index 85e9c421e..db55c20a2 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -3449,6 +3449,24 @@ Setting.create_if_not_exists( state: 'Channel::Filter::Monit', frontend: false ) +Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '5400_postmaster_filter_service_now_check', + area: 'Postmaster::PreFilter', + description: 'Defines postmaster filter to identify service now mails for correct follow-ups.', + options: {}, + state: 'Channel::Filter::ServiceNowCheck', + frontend: false +) +Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '5401_postmaster_filter_service_now_check', + area: 'Postmaster::PostFilter', + description: 'Defines postmaster filter to identify service now mails for correct follow-ups.', + options: {}, + state: 'Channel::Filter::ServiceNowCheck', + frontend: false +) Setting.create_if_not_exists( title: 'Icinga integration', name: 'icinga_integration', diff --git a/spec/models/channel/email_parser_spec.rb b/spec/models/channel/email_parser_spec.rb index 1f68588db..d4c31ad33 100644 --- a/spec/models/channel/email_parser_spec.rb +++ b/spec/models/channel/email_parser_spec.rb @@ -983,6 +983,34 @@ RSpec.describe Channel::EmailParser, type: :model do end end + describe 'ServiceNow handling' do + context 'when emails with service now reference are sent' do + let(:mail_file) { Rails.root.join('test/data/mail/mail089.box') } + let(:mail_file_answer) { Rails.root.join('test/data/mail/mail090.box') } + let(:raw_mail_answer) { File.read(mail_file_answer) } + + it 'does create a ticket with external sync reference' do + expect { described_class.new.process({}, raw_mail) } + .to change(Ticket, :count).by(1) + .and change(Ticket::Article, :count).by(1) + .and change(ExternalSync, :count).by(1) + + expect(ExternalSync.last.source).to eq('ServiceNow') + expect(ExternalSync.last.source_id).to eq('INC678439') + expect(ExternalSync.last.object).to eq('Ticket') + expect(ExternalSync.last.o_id).to eq(Ticket.last.id) + expect(Ticket.last.articles.last.subject).to eq('Incident INC678439 -- zugewiesen an EXT-XXXINIS') + + expect { described_class.new.process({}, raw_mail_answer) } + .to change(Ticket, :count).by(0) + .and change(Ticket::Article, :count).by(1) + .and change(ExternalSync, :count).by(0) + + expect(Ticket.last.articles.last.subject).to eq('Incident INC678439 -- Arbeitsnotizen beigefügt') + end + end + end + describe 'XSS protection' do let(:article) { described_class.new.process({}, raw_mail).second } diff --git a/test/data/mail/mail089.box b/test/data/mail/mail089.box new file mode 100644 index 000000000..0d52c1f65 --- /dev/null +++ b/test/data/mail/mail089.box @@ -0,0 +1,22 @@ +From: IT example +To: support@example.com +Message-ID: <18659453.58107.1576665116411@app129169.gva3.service-now.com> +Subject: Incident INC678439 -- zugewiesen an EXT-XXXINIS +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----=_Part_58105_29005161.1576665006397" +X-ServiceNow-Source: Notification-5555502c4ff4aa0096d7e6101310c734 +X-ServiceNow-SysEmail-Version: 2 +Precedence: bulk +Auto-Submitted: auto-generated +X-ServiceNow-Generated: true + +------=_Part_58105_29005161.1576665006397 +Content-Type: multipart/alternative; + boundary="----=_Part_58106_4969544.1576665006398" + +------=_Part_58106_4969544.1576665006398 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; charset=UTF-8 + +Incident content diff --git a/test/data/mail/mail090.box b/test/data/mail/mail090.box new file mode 100644 index 000000000..70f247e4b --- /dev/null +++ b/test/data/mail/mail090.box @@ -0,0 +1,22 @@ +From: IT example +To: support@example.com +Message-ID: <18659453.58107.1576665116411@app129169.gva3.service-now.com> +Subject: =?UTF-8?Q?Incident_INC678439_--_Arbeitsnotizen_beigef=C3=BCgt?= +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----=_Part_58105_29005161.1576665006397" +X-ServiceNow-Source: Notification-5555502c4ff4aa0096d7e6101310c734 +X-ServiceNow-SysEmail-Version: 2 +Precedence: bulk +Auto-Submitted: auto-generated +X-ServiceNow-Generated: true + +------=_Part_58105_29005161.1576665006397 +Content-Type: multipart/alternative; + boundary="----=_Part_58106_4969544.1576665006398" + +------=_Part_58106_4969544.1576665006398 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; charset=UTF-8 + +Incident content