##// END OF EJS Templates
Ignore emails received from the application emission address to avoid hell cycles (#4139)....
Jean-Philippe Lang -
r2908:cc684803bac9
parent child
Show More
@@ -0,0 +1,19
1 Return-Path: <redmine@somenet.foo>
2 Received: from osiris ([127.0.0.1])
3 by OSIRIS
4 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
5 Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
6 From: "John Doe" <Redmine@example.net>
7 To: <redmine@somenet.foo>
8 Subject: Ticket with the Redmine emission address
9 Date: Sun, 22 Jun 2008 12:28:07 +0200
10 MIME-Version: 1.0
11 Content-Type: text/plain;
12 format=flowed;
13 charset="iso-8859-1";
14 reply-type=original
15 Content-Transfer-Encoding: 7bit
16
17 This is a ticket submitted with the Redmine emission address.
18 It should be ignored.
19
@@ -1,284 +1,290
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MailHandler < ActionMailer::Base
18 class MailHandler < ActionMailer::Base
19 include ActionView::Helpers::SanitizeHelper
19 include ActionView::Helpers::SanitizeHelper
20
20
21 class UnauthorizedAction < StandardError; end
21 class UnauthorizedAction < StandardError; end
22 class MissingInformation < StandardError; end
22 class MissingInformation < StandardError; end
23
23
24 attr_reader :email, :user
24 attr_reader :email, :user
25
25
26 def self.receive(email, options={})
26 def self.receive(email, options={})
27 @@handler_options = options.dup
27 @@handler_options = options.dup
28
28
29 @@handler_options[:issue] ||= {}
29 @@handler_options[:issue] ||= {}
30
30
31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
31 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
32 @@handler_options[:allow_override] ||= []
32 @@handler_options[:allow_override] ||= []
33 # Project needs to be overridable if not specified
33 # Project needs to be overridable if not specified
34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
34 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
35 # Status overridable by default
35 # Status overridable by default
36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
36 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
37 super email
37 super email
38 end
38 end
39
39
40 # Processes incoming emails
40 # Processes incoming emails
41 # Returns the created object (eg. an issue, a message) or false
41 # Returns the created object (eg. an issue, a message) or false
42 def receive(email)
42 def receive(email)
43 @email = email
43 @email = email
44 @user = User.find_by_mail(email.from.to_a.first.to_s.strip)
44 sender_email = email.from.to_a.first.to_s.strip
45 # Ignore emails received from the application emission address to avoid hell cycles
46 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
47 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
48 return false
49 end
50 @user = User.find_by_mail(sender_email)
45 if @user && !@user.active?
51 if @user && !@user.active?
46 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
52 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
47 return false
53 return false
48 end
54 end
49 if @user.nil?
55 if @user.nil?
50 # Email was submitted by an unknown user
56 # Email was submitted by an unknown user
51 case @@handler_options[:unknown_user]
57 case @@handler_options[:unknown_user]
52 when 'accept'
58 when 'accept'
53 @user = User.anonymous
59 @user = User.anonymous
54 when 'create'
60 when 'create'
55 @user = MailHandler.create_user_from_email(email)
61 @user = MailHandler.create_user_from_email(email)
56 if @user
62 if @user
57 logger.info "MailHandler: [#{@user.login}] account created" if logger && logger.info
63 logger.info "MailHandler: [#{@user.login}] account created" if logger && logger.info
58 Mailer.deliver_account_information(@user, @user.password)
64 Mailer.deliver_account_information(@user, @user.password)
59 else
65 else
60 logger.error "MailHandler: could not create account for [#{email.from.first}]" if logger && logger.error
66 logger.error "MailHandler: could not create account for [#{sender_email}]" if logger && logger.error
61 return false
67 return false
62 end
68 end
63 else
69 else
64 # Default behaviour, emails from unknown users are ignored
70 # Default behaviour, emails from unknown users are ignored
65 logger.info "MailHandler: ignoring email from unknown user [#{email.from.first}]" if logger && logger.info
71 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info
66 return false
72 return false
67 end
73 end
68 end
74 end
69 User.current = @user
75 User.current = @user
70 dispatch
76 dispatch
71 end
77 end
72
78
73 private
79 private
74
80
75 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
81 MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
76 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
82 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
77 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]+msg(\d+)\]}
83 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]+msg(\d+)\]}
78
84
79 def dispatch
85 def dispatch
80 headers = [email.in_reply_to, email.references].flatten.compact
86 headers = [email.in_reply_to, email.references].flatten.compact
81 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
87 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
82 klass, object_id = $1, $2.to_i
88 klass, object_id = $1, $2.to_i
83 method_name = "receive_#{klass}_reply"
89 method_name = "receive_#{klass}_reply"
84 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
90 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
85 send method_name, object_id
91 send method_name, object_id
86 else
92 else
87 # ignoring it
93 # ignoring it
88 end
94 end
89 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
95 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
90 receive_issue_reply(m[1].to_i)
96 receive_issue_reply(m[1].to_i)
91 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
97 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
92 receive_message_reply(m[1].to_i)
98 receive_message_reply(m[1].to_i)
93 else
99 else
94 receive_issue
100 receive_issue
95 end
101 end
96 rescue ActiveRecord::RecordInvalid => e
102 rescue ActiveRecord::RecordInvalid => e
97 # TODO: send a email to the user
103 # TODO: send a email to the user
98 logger.error e.message if logger
104 logger.error e.message if logger
99 false
105 false
100 rescue MissingInformation => e
106 rescue MissingInformation => e
101 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
107 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
102 false
108 false
103 rescue UnauthorizedAction => e
109 rescue UnauthorizedAction => e
104 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
110 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
105 false
111 false
106 end
112 end
107
113
108 # Creates a new issue
114 # Creates a new issue
109 def receive_issue
115 def receive_issue
110 project = target_project
116 project = target_project
111 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
117 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
112 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
118 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
113 priority = (get_keyword(:priority) && IssuePriority.find_by_name(get_keyword(:priority)))
119 priority = (get_keyword(:priority) && IssuePriority.find_by_name(get_keyword(:priority)))
114 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
120 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
115
121
116 # check permission
122 # check permission
117 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
123 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
118 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
124 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
119 # check workflow
125 # check workflow
120 if status && issue.new_statuses_allowed_to(user).include?(status)
126 if status && issue.new_statuses_allowed_to(user).include?(status)
121 issue.status = status
127 issue.status = status
122 end
128 end
123 issue.subject = email.subject.chomp
129 issue.subject = email.subject.chomp
124 issue.subject = issue.subject.toutf8 if issue.subject.respond_to?(:toutf8)
130 issue.subject = issue.subject.toutf8 if issue.subject.respond_to?(:toutf8)
125 if issue.subject.blank?
131 if issue.subject.blank?
126 issue.subject = '(no subject)'
132 issue.subject = '(no subject)'
127 end
133 end
128 issue.description = plain_text_body
134 issue.description = plain_text_body
129 # custom fields
135 # custom fields
130 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
136 issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
131 if value = get_keyword(c.name, :override => true)
137 if value = get_keyword(c.name, :override => true)
132 h[c.id] = value
138 h[c.id] = value
133 end
139 end
134 h
140 h
135 end
141 end
136 # add To and Cc as watchers before saving so the watchers can reply to Redmine
142 # add To and Cc as watchers before saving so the watchers can reply to Redmine
137 add_watchers(issue)
143 add_watchers(issue)
138 issue.save!
144 issue.save!
139 add_attachments(issue)
145 add_attachments(issue)
140 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
146 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
141 issue
147 issue
142 end
148 end
143
149
144 def target_project
150 def target_project
145 # TODO: other ways to specify project:
151 # TODO: other ways to specify project:
146 # * parse the email To field
152 # * parse the email To field
147 # * specific project (eg. Setting.mail_handler_target_project)
153 # * specific project (eg. Setting.mail_handler_target_project)
148 target = Project.find_by_identifier(get_keyword(:project))
154 target = Project.find_by_identifier(get_keyword(:project))
149 raise MissingInformation.new('Unable to determine target project') if target.nil?
155 raise MissingInformation.new('Unable to determine target project') if target.nil?
150 target
156 target
151 end
157 end
152
158
153 # Adds a note to an existing issue
159 # Adds a note to an existing issue
154 def receive_issue_reply(issue_id)
160 def receive_issue_reply(issue_id)
155 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
161 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
156
162
157 issue = Issue.find_by_id(issue_id)
163 issue = Issue.find_by_id(issue_id)
158 return unless issue
164 return unless issue
159 # check permission
165 # check permission
160 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
166 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
161 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
167 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
162
168
163 # add the note
169 # add the note
164 journal = issue.init_journal(user, plain_text_body)
170 journal = issue.init_journal(user, plain_text_body)
165 add_attachments(issue)
171 add_attachments(issue)
166 # check workflow
172 # check workflow
167 if status && issue.new_statuses_allowed_to(user).include?(status)
173 if status && issue.new_statuses_allowed_to(user).include?(status)
168 issue.status = status
174 issue.status = status
169 end
175 end
170 issue.save!
176 issue.save!
171 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
177 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
172 journal
178 journal
173 end
179 end
174
180
175 # Reply will be added to the issue
181 # Reply will be added to the issue
176 def receive_journal_reply(journal_id)
182 def receive_journal_reply(journal_id)
177 journal = Journal.find_by_id(journal_id)
183 journal = Journal.find_by_id(journal_id)
178 if journal && journal.journalized_type == 'Issue'
184 if journal && journal.journalized_type == 'Issue'
179 receive_issue_reply(journal.journalized_id)
185 receive_issue_reply(journal.journalized_id)
180 end
186 end
181 end
187 end
182
188
183 # Receives a reply to a forum message
189 # Receives a reply to a forum message
184 def receive_message_reply(message_id)
190 def receive_message_reply(message_id)
185 message = Message.find_by_id(message_id)
191 message = Message.find_by_id(message_id)
186 if message
192 if message
187 message = message.root
193 message = message.root
188 if user.allowed_to?(:add_messages, message.project) && !message.locked?
194 if user.allowed_to?(:add_messages, message.project) && !message.locked?
189 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
195 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
190 :content => plain_text_body)
196 :content => plain_text_body)
191 reply.author = user
197 reply.author = user
192 reply.board = message.board
198 reply.board = message.board
193 message.children << reply
199 message.children << reply
194 add_attachments(reply)
200 add_attachments(reply)
195 reply
201 reply
196 else
202 else
197 raise UnauthorizedAction
203 raise UnauthorizedAction
198 end
204 end
199 end
205 end
200 end
206 end
201
207
202 def add_attachments(obj)
208 def add_attachments(obj)
203 if email.has_attachments?
209 if email.has_attachments?
204 email.attachments.each do |attachment|
210 email.attachments.each do |attachment|
205 Attachment.create(:container => obj,
211 Attachment.create(:container => obj,
206 :file => attachment,
212 :file => attachment,
207 :author => user,
213 :author => user,
208 :content_type => attachment.content_type)
214 :content_type => attachment.content_type)
209 end
215 end
210 end
216 end
211 end
217 end
212
218
213 # Adds To and Cc as watchers of the given object if the sender has the
219 # Adds To and Cc as watchers of the given object if the sender has the
214 # appropriate permission
220 # appropriate permission
215 def add_watchers(obj)
221 def add_watchers(obj)
216 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
222 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
217 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
223 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
218 unless addresses.empty?
224 unless addresses.empty?
219 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
225 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
220 watchers.each {|w| obj.add_watcher(w)}
226 watchers.each {|w| obj.add_watcher(w)}
221 end
227 end
222 end
228 end
223 end
229 end
224
230
225 def get_keyword(attr, options={})
231 def get_keyword(attr, options={})
226 @keywords ||= {}
232 @keywords ||= {}
227 if @keywords.has_key?(attr)
233 if @keywords.has_key?(attr)
228 @keywords[attr]
234 @keywords[attr]
229 else
235 else
230 @keywords[attr] = begin
236 @keywords[attr] = begin
231 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}[ \t]*:[ \t]*(.+)\s*$/i, '')
237 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}[ \t]*:[ \t]*(.+)\s*$/i, '')
232 $1.strip
238 $1.strip
233 elsif !@@handler_options[:issue][attr].blank?
239 elsif !@@handler_options[:issue][attr].blank?
234 @@handler_options[:issue][attr]
240 @@handler_options[:issue][attr]
235 end
241 end
236 end
242 end
237 end
243 end
238 end
244 end
239
245
240 # Returns the text/plain part of the email
246 # Returns the text/plain part of the email
241 # If not found (eg. HTML-only email), returns the body with tags removed
247 # If not found (eg. HTML-only email), returns the body with tags removed
242 def plain_text_body
248 def plain_text_body
243 return @plain_text_body unless @plain_text_body.nil?
249 return @plain_text_body unless @plain_text_body.nil?
244 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
250 parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
245 if parts.empty?
251 if parts.empty?
246 parts << @email
252 parts << @email
247 end
253 end
248 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
254 plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
249 if plain_text_part.nil?
255 if plain_text_part.nil?
250 # no text/plain part found, assuming html-only email
256 # no text/plain part found, assuming html-only email
251 # strip html tags and remove doctype directive
257 # strip html tags and remove doctype directive
252 @plain_text_body = strip_tags(@email.body.to_s)
258 @plain_text_body = strip_tags(@email.body.to_s)
253 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
259 @plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
254 else
260 else
255 @plain_text_body = plain_text_part.body.to_s
261 @plain_text_body = plain_text_part.body.to_s
256 end
262 end
257 @plain_text_body.strip!
263 @plain_text_body.strip!
258 @plain_text_body
264 @plain_text_body
259 end
265 end
260
266
261
267
262 def self.full_sanitizer
268 def self.full_sanitizer
263 @full_sanitizer ||= HTML::FullSanitizer.new
269 @full_sanitizer ||= HTML::FullSanitizer.new
264 end
270 end
265
271
266 # Creates a user account for the +email+ sender
272 # Creates a user account for the +email+ sender
267 def self.create_user_from_email(email)
273 def self.create_user_from_email(email)
268 addr = email.from_addrs.to_a.first
274 addr = email.from_addrs.to_a.first
269 if addr && !addr.spec.blank?
275 if addr && !addr.spec.blank?
270 user = User.new
276 user = User.new
271 user.mail = addr.spec
277 user.mail = addr.spec
272
278
273 names = addr.name.blank? ? addr.spec.gsub(/@.*$/, '').split('.') : addr.name.split
279 names = addr.name.blank? ? addr.spec.gsub(/@.*$/, '').split('.') : addr.name.split
274 user.firstname = names.shift
280 user.firstname = names.shift
275 user.lastname = names.join(' ')
281 user.lastname = names.join(' ')
276 user.lastname = '-' if user.lastname.blank?
282 user.lastname = '-' if user.lastname.blank?
277
283
278 user.login = user.mail
284 user.login = user.mail
279 user.password = ActiveSupport::SecureRandom.hex(5)
285 user.password = ActiveSupport::SecureRandom.hex(5)
280 user.language = Setting.default_language
286 user.language = Setting.default_language
281 user.save ? user : nil
287 user.save ? user : nil
282 end
288 end
283 end
289 end
284 end
290 end
@@ -1,256 +1,263
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class MailHandlerTest < ActiveSupport::TestCase
20 class MailHandlerTest < ActiveSupport::TestCase
21 fixtures :users, :projects,
21 fixtures :users, :projects,
22 :enabled_modules,
22 :enabled_modules,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :workflows,
28 :workflows,
29 :trackers,
29 :trackers,
30 :projects_trackers,
30 :projects_trackers,
31 :enumerations,
31 :enumerations,
32 :issue_categories,
32 :issue_categories,
33 :custom_fields,
33 :custom_fields,
34 :custom_fields_trackers,
34 :custom_fields_trackers,
35 :boards,
35 :boards,
36 :messages
36 :messages
37
37
38 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
38 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
39
39
40 def setup
40 def setup
41 ActionMailer::Base.deliveries.clear
41 ActionMailer::Base.deliveries.clear
42 end
42 end
43
43
44 def test_add_issue
44 def test_add_issue
45 # This email contains: 'Project: onlinestore'
45 # This email contains: 'Project: onlinestore'
46 issue = submit_email('ticket_on_given_project.eml')
46 issue = submit_email('ticket_on_given_project.eml')
47 assert issue.is_a?(Issue)
47 assert issue.is_a?(Issue)
48 assert !issue.new_record?
48 assert !issue.new_record?
49 issue.reload
49 issue.reload
50 assert_equal 'New ticket on a given project', issue.subject
50 assert_equal 'New ticket on a given project', issue.subject
51 assert_equal User.find_by_login('jsmith'), issue.author
51 assert_equal User.find_by_login('jsmith'), issue.author
52 assert_equal Project.find(2), issue.project
52 assert_equal Project.find(2), issue.project
53 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
53 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
54 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
54 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
55 # keywords should be removed from the email body
55 # keywords should be removed from the email body
56 assert !issue.description.match(/^Project:/i)
56 assert !issue.description.match(/^Project:/i)
57 assert !issue.description.match(/^Status:/i)
57 assert !issue.description.match(/^Status:/i)
58 end
58 end
59
59
60 def test_add_issue_with_status
60 def test_add_issue_with_status
61 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
61 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
62 issue = submit_email('ticket_on_given_project.eml')
62 issue = submit_email('ticket_on_given_project.eml')
63 assert issue.is_a?(Issue)
63 assert issue.is_a?(Issue)
64 assert !issue.new_record?
64 assert !issue.new_record?
65 issue.reload
65 issue.reload
66 assert_equal Project.find(2), issue.project
66 assert_equal Project.find(2), issue.project
67 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
67 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
68 end
68 end
69
69
70 def test_add_issue_with_attributes_override
70 def test_add_issue_with_attributes_override
71 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
71 issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
72 assert issue.is_a?(Issue)
72 assert issue.is_a?(Issue)
73 assert !issue.new_record?
73 assert !issue.new_record?
74 issue.reload
74 issue.reload
75 assert_equal 'New ticket on a given project', issue.subject
75 assert_equal 'New ticket on a given project', issue.subject
76 assert_equal User.find_by_login('jsmith'), issue.author
76 assert_equal User.find_by_login('jsmith'), issue.author
77 assert_equal Project.find(2), issue.project
77 assert_equal Project.find(2), issue.project
78 assert_equal 'Feature request', issue.tracker.to_s
78 assert_equal 'Feature request', issue.tracker.to_s
79 assert_equal 'Stock management', issue.category.to_s
79 assert_equal 'Stock management', issue.category.to_s
80 assert_equal 'Urgent', issue.priority.to_s
80 assert_equal 'Urgent', issue.priority.to_s
81 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
81 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
82 end
82 end
83
83
84 def test_add_issue_with_partial_attributes_override
84 def test_add_issue_with_partial_attributes_override
85 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
85 issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
86 assert issue.is_a?(Issue)
86 assert issue.is_a?(Issue)
87 assert !issue.new_record?
87 assert !issue.new_record?
88 issue.reload
88 issue.reload
89 assert_equal 'New ticket on a given project', issue.subject
89 assert_equal 'New ticket on a given project', issue.subject
90 assert_equal User.find_by_login('jsmith'), issue.author
90 assert_equal User.find_by_login('jsmith'), issue.author
91 assert_equal Project.find(2), issue.project
91 assert_equal Project.find(2), issue.project
92 assert_equal 'Feature request', issue.tracker.to_s
92 assert_equal 'Feature request', issue.tracker.to_s
93 assert_nil issue.category
93 assert_nil issue.category
94 assert_equal 'High', issue.priority.to_s
94 assert_equal 'High', issue.priority.to_s
95 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
95 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
96 end
96 end
97
97
98 def test_add_issue_with_spaces_between_attribute_and_separator
98 def test_add_issue_with_spaces_between_attribute_and_separator
99 issue = submit_email('ticket_with_spaces_between_attribute_and_separator.eml', :allow_override => 'tracker,category,priority')
99 issue = submit_email('ticket_with_spaces_between_attribute_and_separator.eml', :allow_override => 'tracker,category,priority')
100 assert issue.is_a?(Issue)
100 assert issue.is_a?(Issue)
101 assert !issue.new_record?
101 assert !issue.new_record?
102 issue.reload
102 issue.reload
103 assert_equal 'New ticket on a given project', issue.subject
103 assert_equal 'New ticket on a given project', issue.subject
104 assert_equal User.find_by_login('jsmith'), issue.author
104 assert_equal User.find_by_login('jsmith'), issue.author
105 assert_equal Project.find(2), issue.project
105 assert_equal Project.find(2), issue.project
106 assert_equal 'Feature request', issue.tracker.to_s
106 assert_equal 'Feature request', issue.tracker.to_s
107 assert_equal 'Stock management', issue.category.to_s
107 assert_equal 'Stock management', issue.category.to_s
108 assert_equal 'Urgent', issue.priority.to_s
108 assert_equal 'Urgent', issue.priority.to_s
109 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
109 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
110 end
110 end
111
111
112
112
113 def test_add_issue_with_attachment_to_specific_project
113 def test_add_issue_with_attachment_to_specific_project
114 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
114 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
115 assert issue.is_a?(Issue)
115 assert issue.is_a?(Issue)
116 assert !issue.new_record?
116 assert !issue.new_record?
117 issue.reload
117 issue.reload
118 assert_equal 'Ticket created by email with attachment', issue.subject
118 assert_equal 'Ticket created by email with attachment', issue.subject
119 assert_equal User.find_by_login('jsmith'), issue.author
119 assert_equal User.find_by_login('jsmith'), issue.author
120 assert_equal Project.find(2), issue.project
120 assert_equal Project.find(2), issue.project
121 assert_equal 'This is a new ticket with attachments', issue.description
121 assert_equal 'This is a new ticket with attachments', issue.description
122 # Attachment properties
122 # Attachment properties
123 assert_equal 1, issue.attachments.size
123 assert_equal 1, issue.attachments.size
124 assert_equal 'Paella.jpg', issue.attachments.first.filename
124 assert_equal 'Paella.jpg', issue.attachments.first.filename
125 assert_equal 'image/jpeg', issue.attachments.first.content_type
125 assert_equal 'image/jpeg', issue.attachments.first.content_type
126 assert_equal 10790, issue.attachments.first.filesize
126 assert_equal 10790, issue.attachments.first.filesize
127 end
127 end
128
128
129 def test_add_issue_with_custom_fields
129 def test_add_issue_with_custom_fields
130 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
130 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
131 assert issue.is_a?(Issue)
131 assert issue.is_a?(Issue)
132 assert !issue.new_record?
132 assert !issue.new_record?
133 issue.reload
133 issue.reload
134 assert_equal 'New ticket with custom field values', issue.subject
134 assert_equal 'New ticket with custom field values', issue.subject
135 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
135 assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
136 assert !issue.description.match(/^searchable field:/i)
136 assert !issue.description.match(/^searchable field:/i)
137 end
137 end
138
138
139 def test_add_issue_with_cc
139 def test_add_issue_with_cc
140 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
140 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
141 assert issue.is_a?(Issue)
141 assert issue.is_a?(Issue)
142 assert !issue.new_record?
142 assert !issue.new_record?
143 issue.reload
143 issue.reload
144 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
144 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
145 assert_equal 1, issue.watchers.size
145 assert_equal 1, issue.watchers.size
146 end
146 end
147
147
148 def test_add_issue_by_unknown_user
148 def test_add_issue_by_unknown_user
149 assert_no_difference 'User.count' do
149 assert_no_difference 'User.count' do
150 assert_equal false, submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'})
150 assert_equal false, submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'})
151 end
151 end
152 end
152 end
153
153
154 def test_add_issue_by_anonymous_user
154 def test_add_issue_by_anonymous_user
155 Role.anonymous.add_permission!(:add_issues)
155 Role.anonymous.add_permission!(:add_issues)
156 assert_no_difference 'User.count' do
156 assert_no_difference 'User.count' do
157 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'accept')
157 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'accept')
158 assert issue.is_a?(Issue)
158 assert issue.is_a?(Issue)
159 assert issue.author.anonymous?
159 assert issue.author.anonymous?
160 end
160 end
161 end
161 end
162
162
163 def test_add_issue_by_created_user
163 def test_add_issue_by_created_user
164 Setting.default_language = 'en'
164 Setting.default_language = 'en'
165 assert_difference 'User.count' do
165 assert_difference 'User.count' do
166 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
166 issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
167 assert issue.is_a?(Issue)
167 assert issue.is_a?(Issue)
168 assert issue.author.active?
168 assert issue.author.active?
169 assert_equal 'john.doe@somenet.foo', issue.author.mail
169 assert_equal 'john.doe@somenet.foo', issue.author.mail
170 assert_equal 'John', issue.author.firstname
170 assert_equal 'John', issue.author.firstname
171 assert_equal 'Doe', issue.author.lastname
171 assert_equal 'Doe', issue.author.lastname
172
172
173 # account information
173 # account information
174 email = ActionMailer::Base.deliveries.first
174 email = ActionMailer::Base.deliveries.first
175 assert_not_nil email
175 assert_not_nil email
176 assert email.subject.include?('account activation')
176 assert email.subject.include?('account activation')
177 login = email.body.match(/\* Login: (.*)$/)[1]
177 login = email.body.match(/\* Login: (.*)$/)[1]
178 password = email.body.match(/\* Password: (.*)$/)[1]
178 password = email.body.match(/\* Password: (.*)$/)[1]
179 assert_equal issue.author, User.try_to_login(login, password)
179 assert_equal issue.author, User.try_to_login(login, password)
180 end
180 end
181 end
181 end
182
182
183 def test_add_issue_without_from_header
183 def test_add_issue_without_from_header
184 Role.anonymous.add_permission!(:add_issues)
184 Role.anonymous.add_permission!(:add_issues)
185 assert_equal false, submit_email('ticket_without_from_header.eml')
185 assert_equal false, submit_email('ticket_without_from_header.eml')
186 end
186 end
187
187
188 def test_should_ignore_emails_from_emission_address
189 Role.anonymous.add_permission!(:add_issues)
190 assert_no_difference 'User.count' do
191 assert_equal false, submit_email('ticket_from_emission_address.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
192 end
193 end
194
188 def test_add_issue_should_send_email_notification
195 def test_add_issue_should_send_email_notification
189 ActionMailer::Base.deliveries.clear
196 ActionMailer::Base.deliveries.clear
190 # This email contains: 'Project: onlinestore'
197 # This email contains: 'Project: onlinestore'
191 issue = submit_email('ticket_on_given_project.eml')
198 issue = submit_email('ticket_on_given_project.eml')
192 assert issue.is_a?(Issue)
199 assert issue.is_a?(Issue)
193 assert_equal 1, ActionMailer::Base.deliveries.size
200 assert_equal 1, ActionMailer::Base.deliveries.size
194 end
201 end
195
202
196 def test_add_issue_note
203 def test_add_issue_note
197 journal = submit_email('ticket_reply.eml')
204 journal = submit_email('ticket_reply.eml')
198 assert journal.is_a?(Journal)
205 assert journal.is_a?(Journal)
199 assert_equal User.find_by_login('jsmith'), journal.user
206 assert_equal User.find_by_login('jsmith'), journal.user
200 assert_equal Issue.find(2), journal.journalized
207 assert_equal Issue.find(2), journal.journalized
201 assert_match /This is reply/, journal.notes
208 assert_match /This is reply/, journal.notes
202 end
209 end
203
210
204 def test_add_issue_note_with_status_change
211 def test_add_issue_note_with_status_change
205 # This email contains: 'Status: Resolved'
212 # This email contains: 'Status: Resolved'
206 journal = submit_email('ticket_reply_with_status.eml')
213 journal = submit_email('ticket_reply_with_status.eml')
207 assert journal.is_a?(Journal)
214 assert journal.is_a?(Journal)
208 issue = Issue.find(journal.issue.id)
215 issue = Issue.find(journal.issue.id)
209 assert_equal User.find_by_login('jsmith'), journal.user
216 assert_equal User.find_by_login('jsmith'), journal.user
210 assert_equal Issue.find(2), journal.journalized
217 assert_equal Issue.find(2), journal.journalized
211 assert_match /This is reply/, journal.notes
218 assert_match /This is reply/, journal.notes
212 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
219 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
213 end
220 end
214
221
215 def test_add_issue_note_should_send_email_notification
222 def test_add_issue_note_should_send_email_notification
216 ActionMailer::Base.deliveries.clear
223 ActionMailer::Base.deliveries.clear
217 journal = submit_email('ticket_reply.eml')
224 journal = submit_email('ticket_reply.eml')
218 assert journal.is_a?(Journal)
225 assert journal.is_a?(Journal)
219 assert_equal 1, ActionMailer::Base.deliveries.size
226 assert_equal 1, ActionMailer::Base.deliveries.size
220 end
227 end
221
228
222 def test_reply_to_a_message
229 def test_reply_to_a_message
223 m = submit_email('message_reply.eml')
230 m = submit_email('message_reply.eml')
224 assert m.is_a?(Message)
231 assert m.is_a?(Message)
225 assert !m.new_record?
232 assert !m.new_record?
226 m.reload
233 m.reload
227 assert_equal 'Reply via email', m.subject
234 assert_equal 'Reply via email', m.subject
228 # The email replies to message #2 which is part of the thread of message #1
235 # The email replies to message #2 which is part of the thread of message #1
229 assert_equal Message.find(1), m.parent
236 assert_equal Message.find(1), m.parent
230 end
237 end
231
238
232 def test_reply_to_a_message_by_subject
239 def test_reply_to_a_message_by_subject
233 m = submit_email('message_reply_by_subject.eml')
240 m = submit_email('message_reply_by_subject.eml')
234 assert m.is_a?(Message)
241 assert m.is_a?(Message)
235 assert !m.new_record?
242 assert !m.new_record?
236 m.reload
243 m.reload
237 assert_equal 'Reply to the first post', m.subject
244 assert_equal 'Reply to the first post', m.subject
238 assert_equal Message.find(1), m.parent
245 assert_equal Message.find(1), m.parent
239 end
246 end
240
247
241 def test_should_strip_tags_of_html_only_emails
248 def test_should_strip_tags_of_html_only_emails
242 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
249 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
243 assert issue.is_a?(Issue)
250 assert issue.is_a?(Issue)
244 assert !issue.new_record?
251 assert !issue.new_record?
245 issue.reload
252 issue.reload
246 assert_equal 'HTML email', issue.subject
253 assert_equal 'HTML email', issue.subject
247 assert_equal 'This is a html-only email.', issue.description
254 assert_equal 'This is a html-only email.', issue.description
248 end
255 end
249
256
250 private
257 private
251
258
252 def submit_email(filename, options={})
259 def submit_email(filename, options={})
253 raw = IO.read(File.join(FIXTURES_PATH, filename))
260 raw = IO.read(File.join(FIXTURES_PATH, filename))
254 MailHandler.receive(raw, options)
261 MailHandler.receive(raw, options)
255 end
262 end
256 end
263 end
General Comments 0
You need to be logged in to leave comments. Login now