end
# Processes incoming emails
+ # Returns the created object (eg. an issue, a message) or false
def receive(email)
@email = email
- @user = User.active.find_by_mail(email.from.to_a.first.to_s.strip)
- unless @user
- # Unknown user => the email is ignored
- # TODO: ability to create the user's account
- logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
+ @user = User.find_by_mail(email.from.to_a.first.to_s.strip)
+ if @user && !@user.active?
+ logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
return false
end
+ if @user.nil?
+ # Email was submitted by an unknown user
+ case @@handler_options[:unknown_user]
+ when 'accept'
+ @user = User.anonymous
+ when 'create'
+ @user = MailHandler.create_user_from_email(email)
+ if @user
+ logger.info "MailHandler: [#{@user.login}] account created" if logger && logger.info
+ Mailer.deliver_account_information(@user, @user.password)
+ else
+ logger.error "MailHandler: could not create account for [#{email.from.first}]" if logger && logger.error
+ return false
+ end
+ else
+ # Default behaviour, emails from unknown users are ignored
+ logger.info "MailHandler: ignoring email from unknown user [#{email.from.first}]" if logger && logger.info
+ return false
+ end
+ end
User.current = @user
dispatch
end
def self.full_sanitizer
@full_sanitizer ||= HTML::FullSanitizer.new
end
+
+ # Creates a user account for the +email+ sender
+ def self.create_user_from_email(email)
+ addr = email.from_addrs.to_a.first
+ if addr && !addr.spec.blank?
+ user = User.new
+ user.mail = addr.spec
+
+ names = addr.name.blank? ? addr.spec.gsub(/@.*$/, '').split('.') : addr.name.split
+ user.firstname = names.shift
+ user.lastname = names.join(' ')
+ user.lastname = '-' if user.lastname.blank?
+
+ user.login = user.mail
+ user.password = ActiveSupport::SecureRandom.hex(5)
+ user.language = Setting.default_language
+ user.save ? user : nil
+ end
+ end
end
# -k, --key Redmine API key
#
# General options:
+# --unknown-user=ACTION how to handle emails from an unknown user
+# ACTION can be one of the following values:
+# ignore: email is ignored (default)
+# accept: accept as anonymous user
+# create: create a user account
# -h, --help show this help
# -v, --verbose show extra information
# -V, --version show version information and exit
class RedmineMailHandler
VERSION = '0.1'
- attr_accessor :verbose, :issue_attributes, :allow_override, :url, :key
+ attr_accessor :verbose, :issue_attributes, :allow_override, :uknown_user, :url, :key
def initialize
self.issue_attributes = {}
[ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT],
[ '--category', GetoptLong::REQUIRED_ARGUMENT],
[ '--priority', GetoptLong::REQUIRED_ARGUMENT],
- [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT]
+ [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT],
+ [ '--unknown-user', GetoptLong::REQUIRED_ARGUMENT]
)
opts.each do |opt, arg|
self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup
when '--allow-override'
self.allow_override = arg.dup
+ when '--unknown-user'
+ self.unknown_user = arg.dup
end
end
def submit(email)
uri = url.gsub(%r{/*$}, '') + '/mail_handler'
- data = { 'key' => key, 'email' => email, 'allow_override' => allow_override }
+ data = { 'key' => key, 'email' => email,
+ 'allow_override' => allow_override,
+ 'unknown_user' => unknown_user }
issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
debug "Posting to #{uri}..."
desc <<-END_DESC\r
Read an email from standard input.\r
\r
+General options:\r
+ unknown_user=ACTION how to handle emails from an unknown user\r
+ ACTION can be one of the following values:\r
+ ignore: email is ignored (default)\r
+ accept: accept as anonymous user\r
+ create: create a user account\r
+ \r
Issue attributes control options:\r
project=PROJECT identifier of the target project\r
status=STATUS name of the target status\r
options = { :issue => {} }\r
%w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }\r
options[:allow_override] = ENV['allow_override'] if ENV['allow_override']\r
+ options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']\r
\r
MailHandler.receive(STDIN.read, options)\r
end\r
desc <<-END_DESC\r
Read emails from an IMAP server.\r
\r
+General options:\r
+ unknown_user=ACTION how to handle emails from an unknown user\r
+ ACTION can be one of the following values:\r
+ ignore: email is ignored (default)\r
+ accept: accept as anonymous user\r
+ create: create a user account\r
+ \r
Available IMAP options:\r
host=HOST IMAP server host (default: 127.0.0.1)\r
port=PORT IMAP server port (default: 143)\r
username=USERNAME IMAP account\r
password=PASSWORD IMAP password\r
folder=FOLDER IMAP folder to read (default: INBOX)\r
-\r
+ \r
Issue attributes control options:\r
project=PROJECT identifier of the target project\r
status=STATUS name of the target status\r
options = { :issue => {} }\r
%w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }\r
options[:allow_override] = ENV['allow_override'] if ENV['allow_override']\r
+ options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']\r
\r
Redmine::IMAP.check(imap_options, options)\r
end\r
--- /dev/null
+Return-Path: <john.doe@somenet.foo>\r
+Received: from osiris ([127.0.0.1])\r
+ by OSIRIS\r
+ with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200\r
+Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>\r
+From: "John Doe" <john.doe@somenet.foo>\r
+To: <redmine@somenet.foo>\r
+Subject: Ticket by unknown user\r
+Date: Sun, 22 Jun 2008 12:28:07 +0200\r
+MIME-Version: 1.0\r
+Content-Type: text/plain;\r
+ format=flowed;\r
+ charset="iso-8859-1";\r
+ reply-type=original\r
+Content-Transfer-Encoding: 7bit\r
+\r
+This is a ticket submitted by an unknown user.\r
+\r
-# redMine - project management software
-# Copyright (C) 2006-2007 Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2009 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
assert_equal 1, issue.watchers.size
end
+ def test_add_issue_by_unknown_user
+ assert_no_difference 'User.count' do
+ assert_equal false, submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'})
+ end
+ end
+
+ def test_add_issue_by_anonymous_user
+ Role.anonymous.add_permission!(:add_issues)
+ assert_no_difference 'User.count' do
+ issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'accept')
+ assert issue.is_a?(Issue)
+ assert issue.author.anonymous?
+ end
+ end
+
+ def test_add_issue_by_created_user
+ Setting.default_language = 'en'
+ assert_difference 'User.count' do
+ issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
+ assert issue.is_a?(Issue)
+ assert issue.author.active?
+ assert_equal 'john.doe@somenet.foo', issue.author.mail
+ assert_equal 'John', issue.author.firstname
+ assert_equal 'Doe', issue.author.lastname
+
+ # account information
+ email = ActionMailer::Base.deliveries.first
+ assert_not_nil email
+ assert email.subject.include?('account activation')
+ login = email.body.match(/\* Login: (.*)$/)[1]
+ password = email.body.match(/\* Password: (.*)$/)[1]
+ assert_equal issue.author, User.try_to_login(login, password)
+ end
+ end
+
def test_add_issue_without_from_header
Role.anonymous.add_permission!(:add_issues)
assert_equal false, submit_email('ticket_without_from_header.eml')