##// END OF EJS Templates
Adds a simple API and a standalone script that can be used to forward emails from a local or remote email server to Redmine (#1110)....
Jean-Philippe Lang -
r1570:25bba80c9eb1
parent child
Show More
@@ -0,0 +1,44
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class MailHandlerController < ActionController::Base
19 before_filter :check_credential
20
21 verify :method => :post,
22 :only => :index,
23 :render => { :nothing => true, :status => 405 }
24
25 # Submits an incoming email to MailHandler
26 def index
27 options = params.dup
28 email = options.delete(:email)
29 if MailHandler.receive(email, options)
30 render :nothing => true, :status => :created
31 else
32 render :nothing => true, :status => :unprocessable_entity
33 end
34 end
35
36 private
37
38 def check_credential
39 User.current = nil
40 unless Setting.mail_handler_api_enabled? && params[:key] == Setting.mail_handler_api_key
41 render :nothing => true, :status => 403
42 end
43 end
44 end
@@ -0,0 +1,19
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module MailHandlerHelper
19 end
@@ -0,0 +1,18
1 <% form_tag({:action => 'edit', :tab => 'mail_handler'}) do %>
2
3 <div class="box tabular settings">
4 <p><label><%= l(:setting_mail_handler_api_enabled) %></label>
5 <%= check_box_tag 'settings[mail_handler_api_enabled]', 1, Setting.mail_handler_api_enabled?,
6 :onclick => "if (this.checked) { Form.Element.enable('settings_mail_handler_api_key'); } else { Form.Element.disable('settings_mail_handler_api_key'); }" %>
7 <%= hidden_field_tag 'settings[mail_handler_api_enabled]', 0 %></p>
8
9 <p><label><%= l(:setting_mail_handler_api_key) %></label>
10 <%= text_field_tag 'settings[mail_handler_api_key]', Setting.mail_handler_api_key,
11 :size => 30,
12 :id => 'settings_mail_handler_api_key',
13 :disabled => !Setting.mail_handler_api_enabled? %>
14 <%= link_to_function l(:label_generate_key), "if ($('settings_mail_handler_api_key').disabled == false) { $('settings_mail_handler_api_key').value = randomKey(20) }" %></p>
15 </div>
16
17 <%= submit_tag l(:button_save) %>
18 <% end %>
@@ -0,0 +1,79
1 #!/usr/bin/ruby
2
3 # rdm-mailhandler
4 # Reads an email from standard input and forward it to a Redmine server
5 # Can be used from a remote mail server
6
7 require 'net/http'
8 require 'net/https'
9 require 'uri'
10 require 'getoptlong'
11
12 class RedmineMailHandler
13 VERSION = '0.1'
14
15 attr_accessor :verbose, :project, :url, :key
16
17 def initialize
18 opts = GetoptLong.new(
19 [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
20 [ '--version', '-V', GetoptLong::NO_ARGUMENT ],
21 [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
22 [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ],
23 [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
24 [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ]
25 )
26
27 opts.each do |opt, arg|
28 case opt
29 when '--url'
30 self.url = arg.dup
31 when '--key'
32 self.key = arg.dup
33 when '--help'
34 usage
35 when '--verbose'
36 self.verbose = true
37 when '--version'
38 puts VERSION; exit
39 when '--project'
40 self.project = arg.dup
41 end
42 end
43
44 usage if url.nil?
45 end
46
47 def submit(email)
48 uri = url.gsub(%r{/*$}, '') + '/mail_handler'
49 debug "Posting to #{uri}..."
50 data = { 'key' => key, 'project' => project, 'email' => email }
51 response = Net::HTTP.post_form(URI.parse(uri), data)
52 debug "Response received: #{response.code}"
53 response.code == 201 ? 0 : 1
54 end
55
56 private
57
58 def usage
59 puts "Usage: rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>"
60 puts "Reads an email from standard input and forward it to a Redmine server"
61 puts
62 puts "Options:"
63 puts " --help show this help"
64 puts " --verbose show extra information"
65 puts " --project identifier of the target project"
66 puts
67 puts "Examples:"
68 puts " rdm-mailhandler --url http://redmine.domain.foo --key secret"
69 puts " rdm-mailhandler --url https://redmine.domain.foo --key secret --project foo"
70 exit
71 end
72
73 def debug(msg)
74 puts msg if verbose
75 end
76 end
77
78 handler = RedmineMailHandler.new
79 handler.submit(STDIN.read)
@@ -0,0 +1,53
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'mail_handler_controller'
20
21 # Re-raise errors caught by the controller.
22 class MailHandlerController; def rescue_action(e) raise e end; end
23
24 class MailHandlerControllerTest < Test::Unit::TestCase
25 fixtures :users, :projects, :enabled_modules, :roles, :members, :issues, :issue_statuses, :trackers, :enumerations
26
27 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
28
29 def setup
30 @controller = MailHandlerController.new
31 @request = ActionController::TestRequest.new
32 @response = ActionController::TestResponse.new
33 User.current = nil
34 end
35
36 def test_should_create_issue
37 # Enable API and set a key
38 Setting.mail_handler_api_enabled = 1
39 Setting.mail_handler_api_key = 'secret'
40
41 post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
42 assert_response 201
43 end
44
45 def test_should_not_allow
46 # Disable API
47 Setting.mail_handler_api_enabled = 0
48 Setting.mail_handler_api_key = 'secret'
49
50 post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
51 assert_response 403
52 end
53 end
@@ -21,6 +21,7 module SettingsHelper
21 21 {:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication},
22 22 {:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking},
23 23 {:name => 'notifications', :partial => 'settings/notifications', :label => l(:field_mail_notification)},
24 {:name => 'mail_handler', :partial => 'settings/mail_handler', :label => l(:label_incoming_emails)},
24 25 {:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural}
25 26 ]
26 27 end
@@ -84,7 +84,7 class MailHandler < ActionMailer::Base
84 84 # TODO: other ways to specify project:
85 85 # * parse the email To field
86 86 # * specific project (eg. Setting.mail_handler_target_project)
87 identifier = if @@handler_options[:project]
87 identifier = if !@@handler_options[:project].blank?
88 88 @@handler_options[:project]
89 89 elsif email.plain_text_body =~ %r{^Project:[ \t]*(.+)$}i
90 90 $1
@@ -101,6 +101,10 notified_events:
101 101 default:
102 102 - issue_added
103 103 - issue_updated
104 mail_handler_api_enabled:
105 default: 0
106 mail_handler_api_key:
107 default:
104 108 issue_list_default_columns:
105 109 serialized: true
106 110 default:
@@ -626,3 +626,7 label_duplicated_by: duplicated by
626 626 setting_enabled_scm: Enabled SCM
627 627 text_enumeration_category_reassign_to: 'Reassign them to this value:'
628 628 text_enumeration_destroy_question: '%d objects are assigned to this value.'
629 label_incoming_emails: Incoming emails
630 label_generate_key: Generate a key
631 setting_mail_handler_api_enabled: Enable WS for incoming emails
632 setting_mail_handler_api_key: API key
@@ -631,3 +631,7 label_duplicated_by: duplicated by
631 631 setting_enabled_scm: Enabled SCM
632 632 text_enumeration_category_reassign_to: 'Reassign them to this value:'
633 633 text_enumeration_destroy_question: '%d objects are assigned to this value.'
634 label_incoming_emails: Incoming emails
635 label_generate_key: Generate a key
636 setting_mail_handler_api_enabled: Enable WS for incoming emails
637 setting_mail_handler_api_key: API key
@@ -628,3 +628,7 label_duplicated_by: duplicated by
628 628 setting_enabled_scm: Enabled SCM
629 629 text_enumeration_category_reassign_to: 'Reassign them to this value:'
630 630 text_enumeration_destroy_question: '%d objects are assigned to this value.'
631 label_incoming_emails: Incoming emails
632 label_generate_key: Generate a key
633 setting_mail_handler_api_enabled: Enable WS for incoming emails
634 setting_mail_handler_api_key: API key
@@ -627,3 +627,7 label_duplicated_by: duplicated by
627 627 setting_enabled_scm: Enabled SCM
628 628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
@@ -214,6 +214,8 setting_user_format: Users display format
214 214 setting_activity_days_default: Days displayed on project activity
215 215 setting_display_subprojects_issues: Display subprojects issues on main projects by default
216 216 setting_enabled_scm: Enabled SCM
217 setting_mail_handler_api_enabled: Enable WS for incoming emails
218 setting_mail_handler_api_key: API key
217 219
218 220 project_module_issue_tracking: Issue tracking
219 221 project_module_time_tracking: Time tracking
@@ -515,6 +517,8 label_preferences: Preferences
515 517 label_chronological_order: In chronological order
516 518 label_reverse_chronological_order: In reverse chronological order
517 519 label_planning: Planning
520 label_incoming_emails: Incoming emails
521 label_generate_key: Generate a key
518 522
519 523 button_login: Login
520 524 button_submit: Submit
@@ -629,3 +629,7 label_duplicated_by: duplicated by
629 629 setting_enabled_scm: Enabled SCM
630 630 text_enumeration_category_reassign_to: 'Reassign them to this value:'
631 631 text_enumeration_destroy_question: '%d objects are assigned to this value.'
632 label_incoming_emails: Incoming emails
633 label_generate_key: Generate a key
634 setting_mail_handler_api_enabled: Enable WS for incoming emails
635 setting_mail_handler_api_key: API key
@@ -626,3 +626,7 label_duplicated_by: duplicated by
626 626 setting_enabled_scm: Enabled SCM
627 627 text_enumeration_category_reassign_to: 'Reassign them to this value:'
628 628 text_enumeration_destroy_question: '%d objects are assigned to this value.'
629 label_incoming_emails: Incoming emails
630 label_generate_key: Generate a key
631 setting_mail_handler_api_enabled: Enable WS for incoming emails
632 setting_mail_handler_api_key: API key
@@ -215,6 +215,8 setting_user_format: Format d'affichage des utilisateurs
215 215 setting_activity_days_default: Nombre de jours affichés sur l'activité des projets
216 216 setting_display_subprojects_issues: Afficher par défaut les demandes des sous-projets sur les projets principaux
217 217 setting_enabled_scm: SCM activés
218 setting_mail_handler_api_enabled: "Activer le WS pour la réception d'emails"
219 setting_mail_handler_api_key: Clé de protection de l'API
218 220
219 221 project_module_issue_tracking: Suivi des demandes
220 222 project_module_time_tracking: Suivi du temps passé
@@ -515,6 +517,8 label_preferences: Préférences
515 517 label_chronological_order: Dans l'ordre chronologique
516 518 label_reverse_chronological_order: Dans l'ordre chronologique inverse
517 519 label_planning: Planning
520 label_incoming_emails: Emails entrants
521 label_generate_key: Générer une clé
518 522
519 523 button_login: Connexion
520 524 button_submit: Soumettre
@@ -626,3 +626,7 label_duplicated_by: duplicated by
626 626 setting_enabled_scm: Enabled SCM
627 627 text_enumeration_category_reassign_to: 'Reassign them to this value:'
628 628 text_enumeration_destroy_question: '%d objects are assigned to this value.'
629 label_incoming_emails: Incoming emails
630 label_generate_key: Generate a key
631 setting_mail_handler_api_enabled: Enable WS for incoming emails
632 setting_mail_handler_api_key: API key
@@ -627,3 +627,7 label_duplicated_by: duplikálta
627 627 setting_enabled_scm: Forráskódkezelő (SCM) engedélyezése
628 628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
@@ -626,3 +626,7 label_duplicated_by: duplicated by
626 626 setting_enabled_scm: Enabled SCM
627 627 text_enumeration_category_reassign_to: 'Reassign them to this value:'
628 628 text_enumeration_destroy_question: '%d objects are assigned to this value.'
629 label_incoming_emails: Incoming emails
630 label_generate_key: Generate a key
631 setting_mail_handler_api_enabled: Enable WS for incoming emails
632 setting_mail_handler_api_key: API key
@@ -627,3 +627,7 label_duplicated_by: duplicated by
627 627 setting_enabled_scm: Enabled SCM
628 628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
@@ -626,3 +626,7 label_duplicated_by: duplicated by
626 626 setting_enabled_scm: Enabled SCM
627 627 text_enumeration_category_reassign_to: 'Reassign them to this value:'
628 628 text_enumeration_destroy_question: '%d objects are assigned to this value.'
629 label_incoming_emails: Incoming emails
630 label_generate_key: Generate a key
631 setting_mail_handler_api_enabled: Enable WS for incoming emails
632 setting_mail_handler_api_key: API key
@@ -628,3 +628,7 label_duplicated_by: duplicated by
628 628 setting_enabled_scm: Enabled SCM
629 629 text_enumeration_category_reassign_to: 'Reassign them to this value:'
630 630 text_enumeration_destroy_question: '%d objects are assigned to this value.'
631 label_incoming_emails: Incoming emails
632 label_generate_key: Generate a key
633 setting_mail_handler_api_enabled: Enable WS for incoming emails
634 setting_mail_handler_api_key: API key
@@ -627,3 +627,7 label_duplicated_by: duplicated by
627 627 setting_enabled_scm: Enabled SCM
628 628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
@@ -627,3 +627,7 enumeration_doc_categories: Dokument-kategorier
627 627 enumeration_activities: Aktiviteter (tidssporing)
628 628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
@@ -626,3 +626,7 label_duplicated_by: duplicated by
626 626 setting_enabled_scm: Enabled SCM
627 627 text_enumeration_category_reassign_to: 'Reassign them to this value:'
628 628 text_enumeration_destroy_question: '%d objects are assigned to this value.'
629 label_incoming_emails: Incoming emails
630 label_generate_key: Generate a key
631 setting_mail_handler_api_enabled: Enable WS for incoming emails
632 setting_mail_handler_api_key: API key
@@ -626,3 +626,7 label_duplicated_by: duplicated by
626 626 setting_enabled_scm: Enabled SCM
627 627 text_enumeration_category_reassign_to: 'Reassign them to this value:'
628 628 text_enumeration_destroy_question: '%d objects are assigned to this value.'
629 label_incoming_emails: Incoming emails
630 label_generate_key: Generate a key
631 setting_mail_handler_api_enabled: Enable WS for incoming emails
632 setting_mail_handler_api_key: API key
@@ -626,3 +626,7 label_duplicated_by: duplicated by
626 626 setting_enabled_scm: Enabled SCM
627 627 text_enumeration_category_reassign_to: 'Reassign them to this value:'
628 628 text_enumeration_destroy_question: '%d objects are assigned to this value.'
629 label_incoming_emails: Incoming emails
630 label_generate_key: Generate a key
631 setting_mail_handler_api_enabled: Enable WS for incoming emails
632 setting_mail_handler_api_key: API key
@@ -626,3 +626,7 label_duplicated_by: duplicated by
626 626 setting_enabled_scm: Enabled SCM
627 627 text_enumeration_category_reassign_to: 'Reassign them to this value:'
628 628 text_enumeration_destroy_question: '%d objects are assigned to this value.'
629 label_incoming_emails: Incoming emails
630 label_generate_key: Generate a key
631 setting_mail_handler_api_enabled: Enable WS for incoming emails
632 setting_mail_handler_api_key: API key
@@ -630,3 +630,7 label_duplicated_by: duplicated by
630 630 setting_enabled_scm: Enabled SCM
631 631 text_enumeration_category_reassign_to: 'Reassign them to this value:'
632 632 text_enumeration_destroy_question: '%d objects are assigned to this value.'
633 label_incoming_emails: Incoming emails
634 label_generate_key: Generate a key
635 setting_mail_handler_api_enabled: Enable WS for incoming emails
636 setting_mail_handler_api_key: API key
@@ -627,3 +627,7 label_duplicated_by: duplicated by
627 627 setting_enabled_scm: Enabled SCM
628 628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
@@ -627,3 +627,7 label_duplicated_by: duplicated by
627 627 setting_enabled_scm: Enabled SCM
628 628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
@@ -629,3 +629,7 label_duplicated_by: duplicated by
629 629 setting_enabled_scm: Enabled SCM
630 630 text_enumeration_category_reassign_to: 'Reassign them to this value:'
631 631 text_enumeration_destroy_question: '%d objects are assigned to this value.'
632 label_incoming_emails: Incoming emails
633 label_generate_key: Generate a key
634 setting_mail_handler_api_enabled: Enable WS for incoming emails
635 setting_mail_handler_api_key: API key
@@ -628,3 +628,7 label_duplicated_by: duplicated by
628 628 setting_enabled_scm: Enabled SCM
629 629 text_enumeration_category_reassign_to: 'Reassign them to this value:'
630 630 text_enumeration_destroy_question: '%d objects are assigned to this value.'
631 label_incoming_emails: Incoming emails
632 label_generate_key: Generate a key
633 setting_mail_handler_api_enabled: Enable WS for incoming emails
634 setting_mail_handler_api_key: API key
@@ -627,3 +627,7 enumeration_doc_categories: 文件分類
627 627 enumeration_activities: 活動 (時間追蹤)
628 628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
@@ -627,3 +627,7 enumeration_doc_categories: 文档类别
627 627 enumeration_activities: 活动(时间跟踪)
628 628 text_enumeration_category_reassign_to: 'Reassign them to this value:'
629 629 text_enumeration_destroy_question: '%d objects are assigned to this value.'
630 label_incoming_emails: Incoming emails
631 label_generate_key: Generate a key
632 setting_mail_handler_api_enabled: Enable WS for incoming emails
633 setting_mail_handler_api_key: API key
@@ -107,6 +107,15 function scmEntryLoaded(id) {
107 107 Element.removeClassName(id, 'loading');
108 108 }
109 109
110 function randomKey(size) {
111 var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
112 var key = '';
113 for (i = 0; i < size; i++) {
114 key += chars[Math.floor(Math.random() * chars.length)];
115 }
116 return key;
117 }
118
110 119 /* shows and hides ajax indicator */
111 120 Ajax.Responders.register({
112 121 onCreate: function(){
General Comments 0
You need to be logged in to leave comments. Login now