OSDN Git Service

Ability to accept incoming emails from unknown users (#2230, #3003).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 14 Jun 2009 14:48:34 +0000 (14:48 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 14 Jun 2009 14:48:34 +0000 (14:48 +0000)
An option lets you specify how to handle emails from unknown users:
* ignore: the email is ignored (previous and default behaviour)
* accept: the sender is considered as an anonymous user
* create: a user account is created (username/password are sent back to the user)

Permissions have to be consistent with the chosen option. Eg. if you choose 'create', the 'Non member' role must have the 'Add issues' permission so that an issue can be created by an unknown user via email. If you choose 'accept', the 'Anonymous' role must have this permission.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2789 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/models/mail_handler.rb
extra/mail_handler/rdm-mailhandler.rb
lib/tasks/email.rake
test/fixtures/mail_handler/ticket_by_unknown_user.eml [new file with mode: 0644]
test/unit/mail_handler_test.rb

index 0276653..6d02ae3 100644 (file)
@@ -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
index 93484ca..2ee5d73 100644 (file)
 #   -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}..."
index 0f74d6b..487ce50 100644 (file)
@@ -21,6 +21,13 @@ namespace :redmine do
     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
@@ -47,6 +54,7 @@ END_DESC
       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
@@ -54,6 +62,13 @@ END_DESC
     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
@@ -61,7 +76,7 @@ Available IMAP options:
   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
@@ -107,6 +122,7 @@ END_DESC
       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
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 (file)
index 0000000..a7abb05
--- /dev/null
@@ -0,0 +1,18 @@
+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
index 295b8ae..2d780d9 100644 (file)
@@ -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')