diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index 4686811d..e1b10991 100644 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -69,6 +69,7 @@ class MailHandler < ActionMailer::Base else # Default behaviour, emails from unknown users are ignored logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info + Mailer.deliver_mail_handler_unauthorized_action(user, email.subject.to_s, :to => sender_email) if Setting.mail_handler_confirmation_on_failure return false end end @@ -102,12 +103,15 @@ class MailHandler < ActionMailer::Base rescue ActiveRecord::RecordInvalid => e # TODO: send a email to the user logger.error e.message if logger + Mailer.deliver_mail_handler_missing_information(user, email.subject.to_s, e.message) if Setting.mail_handler_confirmation_on_failure false rescue MissingInformation => e logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger + Mailer.deliver_mail_handler_missing_information(user, email.subject.to_s, e.message) if Setting.mail_handler_confirmation_on_failure false rescue UnauthorizedAction => e logger.error "MailHandler: unauthorized attempt from #{user}" if logger + Mailer.deliver_mail_handler_unauthorized_action(user, email.subject.to_s) if Setting.mail_handler_confirmation_on_failure false end @@ -141,6 +145,7 @@ class MailHandler < ActionMailer::Base issue.save! add_attachments(issue) logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info + Mailer.deliver_mail_handler_confirmation(issue, user, issue.subject) if Setting.mail_handler_confirmation_on_success issue end @@ -162,6 +167,7 @@ class MailHandler < ActionMailer::Base add_attachments(issue) issue.save! logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info + Mailer.deliver_mail_handler_confirmation(issue.last_journal, user, email.subject) if Setting.mail_handler_confirmation_on_success issue.last_journal end @@ -190,6 +196,7 @@ class MailHandler < ActionMailer::Base reply.board = message.board message.children << reply add_attachments(reply) + Mailer.deliver_mail_handler_confirmation(message, user, reply.subject) if Setting.mail_handler_confirmation_on_success reply else logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info diff --git a/app/models/mailer.rb b/app/models/mailer.rb index b99e211d..962fd870 100644 --- a/app/models/mailer.rb +++ b/app/models/mailer.rb @@ -285,6 +285,44 @@ class Mailer < ActionMailer::Base render_multipart('register', body) end + def mail_handler_confirmation(object, user, email_subject) + recipients user.mail + + case + when object.is_a?(Issue) + project = object.project.name + url = url_for(:controller => 'issues', :action => 'show', :id => object.id) + when object.is_a?(Journal) + project = object.project.name + url = url_for(:controller => 'issues', :action => 'show', :id => object.issue.id) + when object.class == Message + project = object.project.name + url = url_for(object.event_url) + else + project = '' + url = '' + end + + subject "[#{project}] #{l(:label_mail_handler_confirmation, :subject => email_subject)}" + body(:object => object, + :url => url) + render_multipart('mail_handler_confirmation', body) + end + + def mail_handler_unauthorized_action(user, email_subject, options={}) + recipients options[:to] || user.mail + subject l(:label_mail_handler_failure, :subject => email_subject) + body({}) + render_multipart('mail_handler_unauthorized_action', body) + end + + def mail_handler_missing_information(user, email_subject, error_message) + recipients user.mail + subject l(:label_mail_handler_failure, :subject => email_subject) + body({:errors => error_message.to_s}) + render_multipart('mail_handler_missing_information', body) + end + def test(user) redmine_headers 'Type' => "Test" set_language_if_valid(user.language) diff --git a/app/views/mailer/mail_handler_confirmation.text.html.rhtml b/app/views/mailer/mail_handler_confirmation.text.html.rhtml new file mode 100644 index 00000000..5e115e2b --- /dev/null +++ b/app/views/mailer/mail_handler_confirmation.text.html.rhtml @@ -0,0 +1,2 @@ +

<%= l(:text_mail_handler_confirmation_successful) %>
+<%= auto_link(@url) %>

diff --git a/app/views/mailer/mail_handler_confirmation.text.plain.rhtml b/app/views/mailer/mail_handler_confirmation.text.plain.rhtml new file mode 100644 index 00000000..0b8348e4 --- /dev/null +++ b/app/views/mailer/mail_handler_confirmation.text.plain.rhtml @@ -0,0 +1,2 @@ +<%= l(:text_mail_handler_confirmation_successful) %> +<%= @url %> diff --git a/app/views/mailer/mail_handler_missing_information.text.html.rhtml b/app/views/mailer/mail_handler_missing_information.text.html.rhtml new file mode 100644 index 00000000..639e2aa6 --- /dev/null +++ b/app/views/mailer/mail_handler_missing_information.text.html.rhtml @@ -0,0 +1,3 @@ +

<%= l(:label_mail_handler_errors_with_submission) %>

+ +

<%= h(@errors) %>

diff --git a/app/views/mailer/mail_handler_missing_information.text.plain.rhtml b/app/views/mailer/mail_handler_missing_information.text.plain.rhtml new file mode 100644 index 00000000..458f515d --- /dev/null +++ b/app/views/mailer/mail_handler_missing_information.text.plain.rhtml @@ -0,0 +1,3 @@ +<%= l(:label_mail_handler_errors_with_submission) %> + +<%= h(@errors) %> diff --git a/app/views/mailer/mail_handler_unauthorized_action.text.html.rhtml b/app/views/mailer/mail_handler_unauthorized_action.text.html.rhtml new file mode 100644 index 00000000..e1f58ad4 --- /dev/null +++ b/app/views/mailer/mail_handler_unauthorized_action.text.html.rhtml @@ -0,0 +1 @@ +<%= l(:notice_not_authorized_action) %> diff --git a/app/views/mailer/mail_handler_unauthorized_action.text.plain.rhtml b/app/views/mailer/mail_handler_unauthorized_action.text.plain.rhtml new file mode 100644 index 00000000..e1f58ad4 --- /dev/null +++ b/app/views/mailer/mail_handler_unauthorized_action.text.plain.rhtml @@ -0,0 +1 @@ +<%= l(:notice_not_authorized_action) %> diff --git a/app/views/settings/_mail_handler.rhtml b/app/views/settings/_mail_handler.rhtml index b5ed14ea..722ba617 100644 --- a/app/views/settings/_mail_handler.rhtml +++ b/app/views/settings/_mail_handler.rhtml @@ -5,6 +5,9 @@ <%= setting_text_area :mail_handler_body_delimiters, :rows => 5 %>
<%= l(:text_line_separated) %>

+ +

<%= setting_check_box :mail_handler_confirmation_on_success %>

+

<%= setting_check_box :mail_handler_confirmation_on_failure %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index 7fe8846b..2f2752d6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -157,6 +157,7 @@ en: notice_file_not_found: The page you were trying to access doesn't exist or has been removed. notice_locking_conflict: Data has been updated by another user. notice_not_authorized: You are not authorized to access this page. + notice_not_authorized_action: You are not authorized to perform this action. notice_not_authorized_archived_project: The project you're trying to access has been archived. notice_email_sent: "An email was sent to %{value}" notice_email_error: "An error occurred while sending mail (%{value})" @@ -368,6 +369,8 @@ en: setting_commit_logtime_activity_id: Activity for logged time setting_gantt_items_limit: Maximum number of items displayed on the gantt chart setting_issue_startdate_is_adddate: Use current date as start date for new issues + setting_mail_handler_confirmation_on_success: "Send confirmation email on successful incoming email" + setting_mail_handler_confirmation_on_failure: "Send confirmation email on failed incoming email" permission_add_project: Create project permission_add_subprojects: Create subprojects @@ -818,6 +821,9 @@ en: label_path_encoding: Path encoding label_deleted_custom_field: '(deleted custom field)' label_toc: "Contents" + label_mail_handler_confirmation: "Confirmation of email submission: %{subject}" + label_mail_handler_failure: "Failed email submission: %{subject}" + label_mail_handler_errors_with_submission: "There were errors with your email submission:" button_login: Login button_submit: Submit @@ -943,7 +949,8 @@ en: text_git_repo_example: "a bare and local repository (e.g. /gitrepo, c:\\gitrepo)" text_display_subprojects: Display subprojects text_current_project: Current project - + text_mail_handler_confirmation_successful: "Your email has been successful added at the following url" + default_role_manager: Manager default_role_developer: Developer default_role_reporter: Reporter diff --git a/config/settings.yml b/config/settings.yml index 5f708e2d..91139c14 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -178,5 +178,9 @@ default_notification_option: default: 'only_my_events' emails_header: default: '' +mail_handler_confirmation_on_success: + default: 1 +mail_handler_confirmation_on_failure: + default: 1 issue_startdate_is_adddate: - default: 1 \ No newline at end of file + default: 1 diff --git a/test/fixtures/mail_handler/ticket_on_project_with_missing_information.eml b/test/fixtures/mail_handler/ticket_on_project_with_missing_information.eml new file mode 100644 index 00000000..798bb03a --- /dev/null +++ b/test/fixtures/mail_handler/ticket_on_project_with_missing_information.eml @@ -0,0 +1,23 @@ +Return-Path: +Received: from osiris ([127.0.0.1]) + by OSIRIS + with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 +Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> +From: "John Smith" +To: +Subject: New ticket on a given project +Date: Sun, 22 Jun 2008 12:28:07 +0200 +MIME-Version: 1.0 +Content-Type: text/plain; + format=flowed; + charset="iso-8859-1"; + reply-type=original +Content-Transfer-Encoding: 7bit +X-Priority: 3 +X-MSMail-Priority: Normal +X-Mailer: Microsoft Outlook Express 6.00.2900.2869 +X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 + +Test with missing information + +Project: onlinestore diff --git a/test/unit/mail_handler_test.rb b/test/unit/mail_handler_test.rb index a4e68542..0386da11 100644 --- a/test/unit/mail_handler_test.rb +++ b/test/unit/mail_handler_test.rb @@ -1,4 +1,4 @@ -#-- encoding: UTF-8 +#-- encoding: utf-8 -8 #-- copyright # ChiliProject is a project management system. # @@ -39,6 +39,8 @@ class MailHandlerTest < ActiveSupport::TestCase def setup ActionMailer::Base.deliveries.clear Setting.notified_events = Redmine::Notifiable.all.collect(&:name) + Setting.mail_handler_confirmation_on_success = true + Setting.mail_handler_confirmation_on_failure = true end def test_add_issue @@ -67,6 +69,7 @@ class MailHandlerTest < ActiveSupport::TestCase assert !issue.description.match(/^Status:/i) assert !issue.description.match(/^Start Date:/i) # Email notification should be sent + assert_equal 2, ActionMailer::Base.deliveries.size mail = ActionMailer::Base.deliveries.last assert_not_nil mail assert mail.subject.include?('New ticket on a given project') @@ -289,7 +292,7 @@ class MailHandlerTest < ActiveSupport::TestCase # This email contains: 'Project: onlinestore' issue = submit_email('ticket_on_given_project.eml') assert issue.is_a?(Issue) - assert_equal 1, ActionMailer::Base.deliveries.size + assert_equal 2, ActionMailer::Base.deliveries.size end def test_add_issue_note @@ -301,15 +304,6 @@ class MailHandlerTest < ActiveSupport::TestCase assert_equal 'Feature request', journal.issue.tracker.name end - test "reply to issue update (Journal) by message_id" do - journal = submit_email('ticket_reply_by_message_id.eml') - assert journal.is_a?(IssueJournal), "Email was a #{journal.class}" - assert_equal User.find_by_login('jsmith'), journal.user - assert_equal Issue.find(2), journal.journaled - assert_match /This is reply/, journal.notes - assert_equal 'Feature request', journal.issue.tracker.name - end - def test_add_issue_note_with_attribute_changes # This email contains: 'Status: Resolved' journal = submit_email('ticket_reply_with_status.eml') @@ -333,7 +327,7 @@ class MailHandlerTest < ActiveSupport::TestCase ActionMailer::Base.deliveries.clear journal = submit_email('ticket_reply.eml') assert journal.is_a?(Journal) - assert_equal 3, ActionMailer::Base.deliveries.size + assert_equal 1, ActionMailer::Base.deliveries.size end def test_add_issue_note_should_not_set_defaults @@ -456,6 +450,116 @@ class MailHandlerTest < ActiveSupport::TestCase assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255] end + context "with an email that performs an unauthorized action" do + should "deliver an email error confirmation for an unknown user" do + ActionMailer::Base.deliveries.clear + issue = submit_email('ticket_by_unknown_user.eml') + assert_equal false, issue + + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.to.include?('john.doe@somenet.foo') + assert mail.subject.include?('Failed email submission: Ticket by unknown user') + assert mail.body.include?('You are not authorized to perform this action') + end + + should "deliver an email error confirmation for a user without permission" do + ActionMailer::Base.deliveries.clear + # Clear memberships for the sending user so they fail permission checks + Project.find(1).update_attributes(:is_public => false) + Member.all(:conditions => {:user_id => 2}).collect(&:destroy) + assert_no_difference('Journal.count') do + assert_equal false, submit_email('ticket_reply.eml') + end + + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.to.include?('jsmith@somenet.foo') + assert mail.subject.include?('Failed email submission: Re: Add ingredients categories') + assert mail.body.include?('You are not authorized to perform this action') + end + end + + context "with an email that is missing required information" do + should "deliver an email error confirmation to the sender for a missing project" do + ActionMailer::Base.deliveries.clear + issue = submit_email('ticket_with_attachment.eml') # No project set + assert_equal false, issue + + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.to.include?('jsmith@somenet.foo') + assert mail.subject.include?('Failed email submission: Ticket created by email with attachment') + assert mail.body.include?('There were errors with your email submission') + assert mail.body.include?('Unable to determine target project') + + end + + should "deliver an email error confirmation to the sender for a missing other attributes" do + # Add a required custom field to simulate the error + project = Project.find('onlinestore') + project.issue_custom_fields << IssueCustomField.generate(:name => 'Required Custom Field0', :is_required => true, :trackers => project.trackers) + project.save + + ActionMailer::Base.deliveries.clear + issue = submit_email('ticket_on_project_with_missing_information.eml') + assert_equal false, issue + + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.bcc.include?('jsmith@somenet.foo') + assert mail.subject.include?('Failed email submission: New ticket on a given project') + assert mail.body.include?('There were errors with your email submission') + assert mail.body.include?('Required Custom Field0 can\'t be blank') + end + end + + context "#receive_issue" do + should "deliver an email confirmation when configured" do + ActionMailer::Base.deliveries.clear + issue = submit_email('ticket_on_given_project.eml') + + assert_equal 2, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.subject.include?('[OnlineStore]'), "Project name missing" + assert mail.subject.include?('Confirmation of email submission: New ticket on a given project'), "Main subject missing" + assert mail.body.include?("/issues/#{issue.reload.id}"), "Link to issue missing" + end + end + + context "#receive_issue_reply" do + should "deliver an email confirmation when configured" do + journal = submit_email('ticket_reply.eml') + + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.subject.include?('[eCookbook]'), "Project name missing" + assert mail.subject.include?('Confirmation of email submission: Re: Add ingredients categories'), "Main subject missing" + assert mail.body.include?("/issues/2"), "Link to issue missing" + end + + end + + context "#receive_message_reply" do + should "deliver an email confirmation when configured" do + ActionMailer::Base.deliveries.clear + m = submit_email('message_reply.eml') + + assert_equal 3, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.subject.include?('[eCookbook]'), "Project name missing" + assert mail.subject.include?('Confirmation of email submission: Reply via email'), "Main subject missing" + assert mail.body.include?("/boards/1/topics/1"), "Link to message missing" + end + end + private def submit_email(filename, options={})