diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index 02766532..6d02ae3e 100644 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -38,15 +38,34 @@ class MailHandler < ActionMailer::Base 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 @@ -239,4 +258,23 @@ class MailHandler < ActionMailer::Base 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 diff --git a/extra/mail_handler/rdm-mailhandler.rb b/extra/mail_handler/rdm-mailhandler.rb index 93484ca3..2ee5d73d 100644 --- a/extra/mail_handler/rdm-mailhandler.rb +++ b/extra/mail_handler/rdm-mailhandler.rb @@ -15,6 +15,11 @@ # -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 @@ -64,7 +69,7 @@ end 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 = {} @@ -80,7 +85,8 @@ class RedmineMailHandler [ '--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| @@ -99,6 +105,8 @@ class RedmineMailHandler 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 @@ -108,7 +116,9 @@ class RedmineMailHandler 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}..." diff --git a/lib/tasks/email.rake b/lib/tasks/email.rake index 0f74d6bd..487ce506 100644 --- a/lib/tasks/email.rake +++ b/lib/tasks/email.rake @@ -21,6 +21,13 @@ namespace :redmine do desc <<-END_DESC Read an email from standard input. +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 + Issue attributes control options: project=PROJECT identifier of the target project status=STATUS name of the target status @@ -47,6 +54,7 @@ END_DESC options = { :issue => {} } %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] + options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] MailHandler.receive(STDIN.read, options) end @@ -54,6 +62,13 @@ END_DESC desc <<-END_DESC Read emails from an IMAP server. +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 + Available IMAP options: host=HOST IMAP server host (default: 127.0.0.1) port=PORT IMAP server port (default: 143) @@ -61,7 +76,7 @@ Available IMAP options: username=USERNAME IMAP account password=PASSWORD IMAP password folder=FOLDER IMAP folder to read (default: INBOX) - + Issue attributes control options: project=PROJECT identifier of the target project status=STATUS name of the target status @@ -107,6 +122,7 @@ END_DESC options = { :issue => {} } %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] + options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] Redmine::IMAP.check(imap_options, options) end diff --git a/test/fixtures/mail_handler/ticket_by_unknown_user.eml b/test/fixtures/mail_handler/ticket_by_unknown_user.eml new file mode 100644 index 00000000..a7abb05e --- /dev/null +++ b/test/fixtures/mail_handler/ticket_by_unknown_user.eml @@ -0,0 +1,18 @@ +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 Doe" +To: +Subject: Ticket by unknown user +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 + +This is a ticket submitted by an unknown user. + diff --git a/test/unit/mail_handler_test.rb b/test/unit/mail_handler_test.rb index 295b8ae6..2d780d93 100644 --- a/test/unit/mail_handler_test.rb +++ b/test/unit/mail_handler_test.rb @@ -1,5 +1,5 @@ -# 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 @@ -130,6 +130,41 @@ class MailHandlerTest < Test::Unit::TestCase 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')