To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / extra / mail_handler / rdm-mailhandler.rb @ 912:5e80956cc792
History | View | Annotate | Download (6.7 KB)
| 1 | 0:513646585e45 | Chris | #!/usr/bin/env ruby
|
|---|---|---|---|
| 2 | |||
| 3 | # == Synopsis
|
||
| 4 | #
|
||
| 5 | # Reads an email from standard input and forward it to a Redmine server
|
||
| 6 | # through a HTTP request.
|
||
| 7 | #
|
||
| 8 | # == Usage
|
||
| 9 | #
|
||
| 10 | # rdm-mailhandler [options] --url=<Redmine URL> --key=<API key>
|
||
| 11 | #
|
||
| 12 | # == Arguments
|
||
| 13 | #
|
||
| 14 | # -u, --url URL of the Redmine server
|
||
| 15 | # -k, --key Redmine API key
|
||
| 16 | #
|
||
| 17 | # General options:
|
||
| 18 | # --unknown-user=ACTION how to handle emails from an unknown user
|
||
| 19 | # ACTION can be one of the following values:
|
||
| 20 | # ignore: email is ignored (default)
|
||
| 21 | # accept: accept as anonymous user
|
||
| 22 | # create: create a user account
|
||
| 23 | # --no-permission-check disable permission checking when receiving
|
||
| 24 | # the email
|
||
| 25 | 909:cbb26bc654de | Chris | # --key-file=PATH path to a file that contains the Redmine
|
| 26 | # API key (use this option instead of --key
|
||
| 27 | # if you don't the key to appear in the
|
||
| 28 | # command line)
|
||
| 29 | # --no-check-certificate do not check server certificate
|
||
| 30 | 0:513646585e45 | Chris | # -h, --help show this help
|
| 31 | # -v, --verbose show extra information
|
||
| 32 | # -V, --version show version information and exit
|
||
| 33 | #
|
||
| 34 | # Issue attributes control options:
|
||
| 35 | # -p, --project=PROJECT identifier of the target project
|
||
| 36 | # -s, --status=STATUS name of the target status
|
||
| 37 | # -t, --tracker=TRACKER name of the target tracker
|
||
| 38 | # --category=CATEGORY name of the target category
|
||
| 39 | # --priority=PRIORITY name of the target priority
|
||
| 40 | # -o, --allow-override=ATTRS allow email content to override attributes
|
||
| 41 | # specified by previous options
|
||
| 42 | # ATTRS is a comma separated list of attributes
|
||
| 43 | #
|
||
| 44 | # == Examples
|
||
| 45 | # No project specified. Emails MUST contain the 'Project' keyword:
|
||
| 46 | #
|
||
| 47 | # rdm-mailhandler --url http://redmine.domain.foo --key secret
|
||
| 48 | #
|
||
| 49 | # Fixed project and default tracker specified, but emails can override
|
||
| 50 | # both tracker and priority attributes using keywords:
|
||
| 51 | #
|
||
| 52 | # rdm-mailhandler --url https://domain.foo/redmine --key secret \\
|
||
| 53 | # --project foo \\
|
||
| 54 | # --tracker bug \\
|
||
| 55 | # --allow-override tracker,priority
|
||
| 56 | |||
| 57 | require 'net/http'
|
||
| 58 | require 'net/https'
|
||
| 59 | require 'uri'
|
||
| 60 | require 'getoptlong'
|
||
| 61 | require 'rdoc/usage'
|
||
| 62 | |||
| 63 | module Net |
||
| 64 | class HTTPS < HTTP |
||
| 65 | 909:cbb26bc654de | Chris | def self.post_form(url, params, headers, options={}) |
| 66 | 0:513646585e45 | Chris | request = Post.new(url.path)
|
| 67 | request.form_data = params |
||
| 68 | request.basic_auth url.user, url.password if url.user
|
||
| 69 | 128:07fa8a8b56a8 | Chris | request.initialize_http_header(headers) |
| 70 | 0:513646585e45 | Chris | http = new(url.host, url.port) |
| 71 | http.use_ssl = (url.scheme == 'https')
|
||
| 72 | 909:cbb26bc654de | Chris | if options[:no_check_certificate] |
| 73 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE |
||
| 74 | end
|
||
| 75 | 0:513646585e45 | Chris | http.start {|h| h.request(request) }
|
| 76 | end
|
||
| 77 | end
|
||
| 78 | end
|
||
| 79 | |||
| 80 | class RedmineMailHandler |
||
| 81 | VERSION = '0.1' |
||
| 82 | |||
| 83 | 909:cbb26bc654de | Chris | attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :no_permission_check, :url, :key, :no_check_certificate |
| 84 | 0:513646585e45 | Chris | |
| 85 | def initialize |
||
| 86 | self.issue_attributes = {}
|
||
| 87 | |||
| 88 | opts = GetoptLong.new(
|
||
| 89 | [ '--help', '-h', GetoptLong::NO_ARGUMENT ], |
||
| 90 | [ '--version', '-V', GetoptLong::NO_ARGUMENT ], |
||
| 91 | [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], |
||
| 92 | [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ], |
||
| 93 | [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT], |
||
| 94 | 909:cbb26bc654de | Chris | [ '--key-file', GetoptLong::REQUIRED_ARGUMENT], |
| 95 | 0:513646585e45 | Chris | [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ], |
| 96 | [ '--status', '-s', GetoptLong::REQUIRED_ARGUMENT ], |
||
| 97 | [ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT], |
||
| 98 | [ '--category', GetoptLong::REQUIRED_ARGUMENT], |
||
| 99 | [ '--priority', GetoptLong::REQUIRED_ARGUMENT], |
||
| 100 | [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT], |
||
| 101 | [ '--unknown-user', GetoptLong::REQUIRED_ARGUMENT], |
||
| 102 | 909:cbb26bc654de | Chris | [ '--no-permission-check', GetoptLong::NO_ARGUMENT], |
| 103 | [ '--no-check-certificate', GetoptLong::NO_ARGUMENT] |
||
| 104 | 0:513646585e45 | Chris | ) |
| 105 | |||
| 106 | opts.each do |opt, arg|
|
||
| 107 | case opt
|
||
| 108 | when '--url' |
||
| 109 | self.url = arg.dup
|
||
| 110 | when '--key' |
||
| 111 | self.key = arg.dup
|
||
| 112 | 909:cbb26bc654de | Chris | when '--key-file' |
| 113 | begin
|
||
| 114 | self.key = File.read(arg).strip |
||
| 115 | rescue Exception => e |
||
| 116 | $stderr.puts "Unable to read the key from #{arg}: #{e.message}" |
||
| 117 | exit 1
|
||
| 118 | end
|
||
| 119 | 0:513646585e45 | Chris | when '--help' |
| 120 | usage |
||
| 121 | when '--verbose' |
||
| 122 | self.verbose = true |
||
| 123 | when '--version' |
||
| 124 | puts VERSION; exit
|
||
| 125 | when '--project', '--status', '--tracker', '--category', '--priority' |
||
| 126 | self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup |
||
| 127 | when '--allow-override' |
||
| 128 | self.allow_override = arg.dup
|
||
| 129 | when '--unknown-user' |
||
| 130 | self.unknown_user = arg.dup
|
||
| 131 | when '--no-permission-check' |
||
| 132 | self.no_permission_check = '1' |
||
| 133 | 909:cbb26bc654de | Chris | when '--no-check-certificate' |
| 134 | self.no_check_certificate = true |
||
| 135 | 0:513646585e45 | Chris | end
|
| 136 | end
|
||
| 137 | |||
| 138 | RDoc.usage if url.nil? |
||
| 139 | end
|
||
| 140 | |||
| 141 | def submit(email) |
||
| 142 | uri = url.gsub(%r{/*$}, '') + '/mail_handler' |
||
| 143 | |||
| 144 | 128:07fa8a8b56a8 | Chris | headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" }
|
| 145 | |||
| 146 | 0:513646585e45 | Chris | data = { 'key' => key, 'email' => email,
|
| 147 | 'allow_override' => allow_override,
|
||
| 148 | 'unknown_user' => unknown_user,
|
||
| 149 | 'no_permission_check' => no_permission_check}
|
||
| 150 | issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value }
|
||
| 151 | |||
| 152 | debug "Posting to #{uri}..."
|
||
| 153 | 909:cbb26bc654de | Chris | response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate) |
| 154 | 0:513646585e45 | Chris | debug "Response received: #{response.code}"
|
| 155 | |||
| 156 | case response.code.to_i
|
||
| 157 | when 403 |
||
| 158 | warn "Request was denied by your Redmine server. " +
|
||
| 159 | "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key."
|
||
| 160 | return 77 |
||
| 161 | when 422 |
||
| 162 | warn "Request was denied by your Redmine server. " +
|
||
| 163 | "Possible reasons: email is sent from an invalid email address or is missing some information."
|
||
| 164 | return 77 |
||
| 165 | when 400..499 |
||
| 166 | warn "Request was denied by your Redmine server (#{response.code})."
|
||
| 167 | return 77 |
||
| 168 | when 500..599 |
||
| 169 | warn "Failed to contact your Redmine server (#{response.code})."
|
||
| 170 | return 75 |
||
| 171 | when 201 |
||
| 172 | debug "Proccessed successfully"
|
||
| 173 | return 0 |
||
| 174 | else
|
||
| 175 | return 1 |
||
| 176 | end
|
||
| 177 | end
|
||
| 178 | |||
| 179 | private |
||
| 180 | |||
| 181 | def debug(msg) |
||
| 182 | puts msg if verbose
|
||
| 183 | end
|
||
| 184 | end
|
||
| 185 | |||
| 186 | handler = RedmineMailHandler.new
|
||
| 187 | exit(handler.submit(STDIN.read)) |