Browse Source

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
tags/0.8.0-RC1
Jean-Philippe Lang 16 years ago
parent
commit
25bba80c9e

+ 44
- 0
app/controllers/mail_handler_controller.rb 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

+ 19
- 0
app/helpers/mail_handler_helper.rb 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

+ 1
- 0
app/helpers/settings_helper.rb 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

+ 1
- 1
app/models/mail_handler.rb 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

+ 18
- 0
app/views/settings/_mail_handler.rhtml 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 %>

+ 4
- 0
config/settings.yml 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:

+ 79
- 0
extra/mail_handler/rdm-mailhandler.rb 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)

+ 4
- 0
lang/bg.yml 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

+ 4
- 0
lang/cs.yml 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

+ 4
- 0
lang/da.yml 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

+ 4
- 0
lang/de.yml 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

+ 4
- 0
lang/en.yml 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

+ 4
- 0
lang/es.yml 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

+ 4
- 0
lang/fi.yml 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

+ 4
- 0
lang/fr.yml 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

+ 4
- 0
lang/he.yml 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

+ 4
- 0
lang/hu.yml 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

+ 4
- 0
lang/it.yml 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

+ 4
- 0
lang/ja.yml 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

+ 4
- 0
lang/ko.yml 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

+ 4
- 0
lang/lt.yml 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

+ 4
- 0
lang/nl.yml 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

+ 4
- 0
lang/no.yml 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

+ 4
- 0
lang/pl.yml 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

+ 4
- 0
lang/pt-br.yml 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

+ 4
- 0
lang/pt.yml 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

+ 4
- 0
lang/ro.yml 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

+ 4
- 0
lang/ru.yml 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

+ 4
- 0
lang/sr.yml 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

+ 4
- 0
lang/sv.yml 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

+ 4
- 0
lang/th.yml 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

+ 4
- 0
lang/uk.yml 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

+ 4
- 0
lang/zh-tw.yml 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

+ 4
- 0
lang/zh.yml 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

+ 9
- 0
public/javascripts/application.js 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(){

+ 53
- 0
test/functional/mail_handler_controller_test.rb 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

Loading…
Cancel
Save