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).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@1584 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2008-06-25 19:25:28 +00:00
parent 4d6f50d3fe
commit 25bba80c9e
36 changed files with 336 additions and 1 deletions

View File

@ -0,0 +1,44 @@
# redMine - project management software
# Copyright (C) 2006-2008 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class MailHandlerController < ActionController::Base
before_filter :check_credential
verify :method => :post,
:only => :index,
:render => { :nothing => true, :status => 405 }
# Submits an incoming email to MailHandler
def index
options = params.dup
email = options.delete(:email)
if MailHandler.receive(email, options)
render :nothing => true, :status => :created
else
render :nothing => true, :status => :unprocessable_entity
end
end
private
def check_credential
User.current = nil
unless Setting.mail_handler_api_enabled? && params[:key] == Setting.mail_handler_api_key
render :nothing => true, :status => 403
end
end
end

View File

@ -0,0 +1,19 @@
# redMine - project management software
# Copyright (C) 2006-2008 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MailHandlerHelper
end

View File

@ -21,6 +21,7 @@ module SettingsHelper
{:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication},
{:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking},
{:name => 'notifications', :partial => 'settings/notifications', :label => l(:field_mail_notification)},
{:name => 'mail_handler', :partial => 'settings/mail_handler', :label => l(:label_incoming_emails)},
{:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural}
]
end

View File

@ -84,7 +84,7 @@ class MailHandler < ActionMailer::Base
# TODO: other ways to specify project:
# * parse the email To field
# * specific project (eg. Setting.mail_handler_target_project)
identifier = if @@handler_options[:project]
identifier = if !@@handler_options[:project].blank?
@@handler_options[:project]
elsif email.plain_text_body =~ %r{^Project:[ \t]*(.+)$}i
$1

View File

@ -0,0 +1,18 @@
<% form_tag({:action => 'edit', :tab => 'mail_handler'}) do %>
<div class="box tabular settings">
<p><label><%= l(:setting_mail_handler_api_enabled) %></label>
<%= check_box_tag 'settings[mail_handler_api_enabled]', 1, Setting.mail_handler_api_enabled?,
:onclick => "if (this.checked) { Form.Element.enable('settings_mail_handler_api_key'); } else { Form.Element.disable('settings_mail_handler_api_key'); }" %>
<%= hidden_field_tag 'settings[mail_handler_api_enabled]', 0 %></p>
<p><label><%= l(:setting_mail_handler_api_key) %></label>
<%= text_field_tag 'settings[mail_handler_api_key]', Setting.mail_handler_api_key,
:size => 30,
:id => 'settings_mail_handler_api_key',
:disabled => !Setting.mail_handler_api_enabled? %>
<%= link_to_function l(:label_generate_key), "if ($('settings_mail_handler_api_key').disabled == false) { $('settings_mail_handler_api_key').value = randomKey(20) }" %></p>
</div>
<%= submit_tag l(:button_save) %>
<% end %>

View File

@ -101,6 +101,10 @@ notified_events:
default:
- issue_added
- issue_updated
mail_handler_api_enabled:
default: 0
mail_handler_api_key:
default:
issue_list_default_columns:
serialized: true
default:

View File

@ -0,0 +1,79 @@
#!/usr/bin/ruby
# rdm-mailhandler
# Reads an email from standard input and forward it to a Redmine server
# Can be used from a remote mail server
require 'net/http'
require 'net/https'
require 'uri'
require 'getoptlong'
class RedmineMailHandler
VERSION = '0.1'
attr_accessor :verbose, :project, :url, :key
def initialize
opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
[ '--version', '-V', GetoptLong::NO_ARGUMENT ],
[ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
[ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ],
[ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
[ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ]
)
opts.each do |opt, arg|
case opt
when '--url'
self.url = arg.dup
when '--key'
self.key = arg.dup
when '--help'
usage
when '--verbose'
self.verbose = true
when '--version'
puts VERSION; exit
when '--project'
self.project = arg.dup
end
end
usage if url.nil?
end
def submit(email)
uri = url.gsub(%r{/*$}, '') + '/mail_handler'
debug "Posting to #{uri}..."
data = { 'key' => key, 'project' => project, 'email' => email }
response = Net::HTTP.post_form(URI.parse(uri), data)
debug "Response received: #{response.code}"
response.code == 201 ? 0 : 1
end
private
def usage
puts "Usage: rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>"
puts "Reads an email from standard input and forward it to a Redmine server"
puts
puts "Options:"
puts " --help show this help"
puts " --verbose show extra information"
puts " --project identifier of the target project"
puts
puts "Examples:"
puts " rdm-mailhandler --url http://redmine.domain.foo --key secret"
puts " rdm-mailhandler --url https://redmine.domain.foo --key secret --project foo"
exit
end
def debug(msg)
puts msg if verbose
end
end
handler = RedmineMailHandler.new
handler.submit(STDIN.read)

View File

@ -626,3 +626,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -631,3 +631,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -628,3 +628,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -627,3 +627,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -214,6 +214,8 @@ setting_user_format: Users display format
setting_activity_days_default: Days displayed on project activity
setting_display_subprojects_issues: Display subprojects issues on main projects by default
setting_enabled_scm: Enabled SCM
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key
project_module_issue_tracking: Issue tracking
project_module_time_tracking: Time tracking
@ -515,6 +517,8 @@ label_preferences: Preferences
label_chronological_order: In chronological order
label_reverse_chronological_order: In reverse chronological order
label_planning: Planning
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
button_login: Login
button_submit: Submit

View File

@ -629,3 +629,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -626,3 +626,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -215,6 +215,8 @@ setting_user_format: Format d'affichage des utilisateurs
setting_activity_days_default: Nombre de jours affichés sur l'activité des projets
setting_display_subprojects_issues: Afficher par défaut les demandes des sous-projets sur les projets principaux
setting_enabled_scm: SCM activés
setting_mail_handler_api_enabled: "Activer le WS pour la réception d'emails"
setting_mail_handler_api_key: Clé de protection de l'API
project_module_issue_tracking: Suivi des demandes
project_module_time_tracking: Suivi du temps passé
@ -515,6 +517,8 @@ label_preferences: Préférences
label_chronological_order: Dans l'ordre chronologique
label_reverse_chronological_order: Dans l'ordre chronologique inverse
label_planning: Planning
label_incoming_emails: Emails entrants
label_generate_key: Générer une clé
button_login: Connexion
button_submit: Soumettre

View File

@ -626,3 +626,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -627,3 +627,7 @@ label_duplicated_by: duplikálta
setting_enabled_scm: Forráskódkezelő (SCM) engedélyezése
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -626,3 +626,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -627,3 +627,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -626,3 +626,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -628,3 +628,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -627,3 +627,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -627,3 +627,7 @@ enumeration_doc_categories: Dokument-kategorier
enumeration_activities: Aktiviteter (tidssporing)
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -626,3 +626,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -626,3 +626,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -626,3 +626,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -626,3 +626,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -630,3 +630,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -627,3 +627,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -627,3 +627,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -629,3 +629,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -628,3 +628,7 @@ label_duplicated_by: duplicated by
setting_enabled_scm: Enabled SCM
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -627,3 +627,7 @@ enumeration_doc_categories: 文件分類
enumeration_activities: 活動 (時間追蹤)
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -627,3 +627,7 @@ enumeration_doc_categories: 文档类别
enumeration_activities: 活动(时间跟踪)
text_enumeration_category_reassign_to: 'Reassign them to this value:'
text_enumeration_destroy_question: '%d objects are assigned to this value.'
label_incoming_emails: Incoming emails
label_generate_key: Generate a key
setting_mail_handler_api_enabled: Enable WS for incoming emails
setting_mail_handler_api_key: API key

View File

@ -107,6 +107,15 @@ function scmEntryLoaded(id) {
Element.removeClassName(id, 'loading');
}
function randomKey(size) {
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');
var key = '';
for (i = 0; i < size; i++) {
key += chars[Math.floor(Math.random() * chars.length)];
}
return key;
}
/* shows and hides ajax indicator */
Ajax.Responders.register({
onCreate: function(){

View File

@ -0,0 +1,53 @@
# redMine - project management software
# Copyright (C) 2006-2008 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require File.dirname(__FILE__) + '/../test_helper'
require 'mail_handler_controller'
# Re-raise errors caught by the controller.
class MailHandlerController; def rescue_action(e) raise e end; end
class MailHandlerControllerTest < Test::Unit::TestCase
fixtures :users, :projects, :enabled_modules, :roles, :members, :issues, :issue_statuses, :trackers, :enumerations
FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
def setup
@controller = MailHandlerController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
User.current = nil
end
def test_should_create_issue
# Enable API and set a key
Setting.mail_handler_api_enabled = 1
Setting.mail_handler_api_key = 'secret'
post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
assert_response 201
end
def test_should_not_allow
# Disable API
Setting.mail_handler_api_enabled = 0
Setting.mail_handler_api_key = 'secret'
post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
assert_response 403
end
end