gitlab-mail_room 0.0.4 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitlab/issue_templates/Default.md +9 -0
- data/.gitlab/issue_templates/Release.md +8 -0
- data/.gitlab-ci.yml +18 -16
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +494 -0
- data/.ruby-version +1 -1
- data/.travis.yml +12 -5
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +40 -0
- data/README.md +136 -10
- data/Rakefile +1 -1
- data/lib/mail_room/arbitration/redis.rb +1 -1
- data/lib/mail_room/cli.rb +2 -2
- data/lib/mail_room/configuration.rb +11 -1
- data/lib/mail_room/connection.rb +10 -177
- data/lib/mail_room/coordinator.rb +8 -4
- data/lib/mail_room/crash_handler.rb +8 -12
- data/lib/mail_room/delivery/letter_opener.rb +1 -1
- data/lib/mail_room/delivery/postback.rb +36 -4
- data/lib/mail_room/delivery/sidekiq.rb +4 -3
- data/lib/mail_room/health_check.rb +60 -0
- data/lib/mail_room/imap/connection.rb +200 -0
- data/lib/mail_room/imap/message.rb +19 -0
- data/lib/mail_room/imap.rb +8 -0
- data/lib/mail_room/jwt.rb +39 -0
- data/lib/mail_room/logger/structured.rb +15 -1
- data/lib/mail_room/mailbox.rb +62 -20
- data/lib/mail_room/mailbox_watcher.rb +15 -2
- data/lib/mail_room/message.rb +16 -0
- data/lib/mail_room/microsoft_graph/connection.rb +243 -0
- data/lib/mail_room/microsoft_graph.rb +7 -0
- data/lib/mail_room/version.rb +2 -2
- data/lib/mail_room.rb +2 -0
- data/mail_room.gemspec +13 -4
- data/spec/fixtures/jwt_secret +1 -0
- data/spec/fixtures/test_config.yml +3 -0
- data/spec/lib/arbitration/redis_spec.rb +9 -7
- data/spec/lib/cli_spec.rb +32 -17
- data/spec/lib/configuration_spec.rb +10 -3
- data/spec/lib/coordinator_spec.rb +27 -11
- data/spec/lib/crash_handler_spec.rb +10 -9
- data/spec/lib/delivery/letter_opener_spec.rb +10 -6
- data/spec/lib/delivery/logger_spec.rb +8 -10
- data/spec/lib/delivery/postback_spec.rb +73 -41
- data/spec/lib/delivery/que_spec.rb +5 -8
- data/spec/lib/delivery/sidekiq_spec.rb +33 -11
- data/spec/lib/health_check_spec.rb +57 -0
- data/spec/lib/{connection_spec.rb → imap/connection_spec.rb} +13 -17
- data/spec/lib/imap/message_spec.rb +36 -0
- data/spec/lib/jwt_spec.rb +80 -0
- data/spec/lib/logger/structured_spec.rb +34 -2
- data/spec/lib/mailbox_spec.rb +79 -34
- data/spec/lib/mailbox_watcher_spec.rb +54 -41
- data/spec/lib/message_spec.rb +35 -0
- data/spec/lib/microsoft_graph/connection_spec.rb +252 -0
- data/spec/spec_helper.rb +14 -4
- metadata +130 -21
data/README.md
CHANGED
@@ -20,7 +20,14 @@ The fork is useful as we can post quick fixes to our own fork and release fixes
|
|
20
20
|
|
21
21
|
## README
|
22
22
|
|
23
|
-
mail_room is a configuration based process that will
|
23
|
+
mail_room is a configuration based process that will listen for incoming
|
24
|
+
e-mail and execute a delivery method when a new message is
|
25
|
+
received. mail_room supports the following methods for receiving e-mail:
|
26
|
+
|
27
|
+
* IMAP
|
28
|
+
* [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/api/resources/mail-api-overview?view=graph-rest-1.0)
|
29
|
+
|
30
|
+
Examples of delivery methods include:
|
24
31
|
|
25
32
|
* POST to a delivery URL (Postback)
|
26
33
|
* Queue a job to Sidekiq or Que for later processing (Sidekiq or Que)
|
@@ -55,6 +62,9 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
|
|
55
62
|
|
56
63
|
```yaml
|
57
64
|
---
|
65
|
+
:health_check:
|
66
|
+
:address: "127.0.0.1"
|
67
|
+
:port: 8080
|
58
68
|
:mailboxes:
|
59
69
|
-
|
60
70
|
:email: "user1@gmail.com"
|
@@ -114,11 +124,135 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
|
|
114
124
|
:host: 127.0.0.1
|
115
125
|
:port: 26379
|
116
126
|
:worker: EmailReceiverWorker
|
127
|
+
-
|
128
|
+
:email: "user7@outlook365.com"
|
129
|
+
:password: "password"
|
130
|
+
:name: "inbox"
|
131
|
+
:inbox_method: microsoft_graph
|
132
|
+
:inbox_options:
|
133
|
+
:tenant_id: 12345
|
134
|
+
:client_id: ABCDE
|
135
|
+
:client_secret: YOUR-SECRET-HERE
|
136
|
+
:poll_interval: 60
|
137
|
+
:azure_ad_endpoint: https://login.microsoftonline.com
|
138
|
+
:graph_endpoint: https://graph.microsoft.com
|
139
|
+
:delivery_method: sidekiq
|
140
|
+
:delivery_options:
|
141
|
+
:redis_url: redis://localhost:6379
|
142
|
+
:worker: EmailReceiverWorker
|
143
|
+
-
|
144
|
+
:email: "user8@gmail.com"
|
145
|
+
:password: "password"
|
146
|
+
:name: "inbox"
|
147
|
+
:delivery_method: postback
|
148
|
+
:delivery_options:
|
149
|
+
:delivery_url: "http://localhost:3000/inbox"
|
150
|
+
:jwt_auth_header: "Mailroom-Api-Request"
|
151
|
+
:jwt_issuer: "mailroom"
|
152
|
+
:jwt_algorithm: "HS256"
|
153
|
+
:jwt_secret_path: "/etc/secrets/mailroom/.mailroom_secret"
|
117
154
|
```
|
118
155
|
|
119
156
|
**Note:** If using `delete_after_delivery`, you also probably want to use
|
120
157
|
`expunge_deleted` unless you really know what you're doing.
|
121
158
|
|
159
|
+
## health_check ##
|
160
|
+
|
161
|
+
Requires `webrick` gem to be installed.
|
162
|
+
|
163
|
+
This option enables an HTTP server that listens to a bind address
|
164
|
+
defined by `address` and `port`. The following endpoints are supported:
|
165
|
+
|
166
|
+
* `/liveness`: This returns a 200 status code with `OK` as the body if
|
167
|
+
the server is running. Otherwise, it returns a 500 status code.
|
168
|
+
|
169
|
+
This feature is not included in upstream `mail_room` and is specific to GitLab.
|
170
|
+
|
171
|
+
## inbox_method
|
172
|
+
|
173
|
+
By default, IMAP mode is assumed for reading a mailbox.
|
174
|
+
|
175
|
+
### IMAP Server Configuration ##
|
176
|
+
|
177
|
+
You can set per-mailbox configuration for the IMAP server's `host` (default: 'imap.gmail.com'), `port` (default: 993), `ssl` (default: true), and `start_tls` (default: false).
|
178
|
+
|
179
|
+
If you want to set additional options for IMAP SSL you can pass a YAML hash to match [SSLContext#set_params](http://docs.ruby-lang.org/en/2.2.0/OpenSSL/SSL/SSLContext.html#method-i-set_params). If you set `verify_mode` to `:none` it'll replace with the appropriate constant.
|
180
|
+
|
181
|
+
If you're seeing the error `Please log in via your web browser: https://support.google.com/mail/accounts/answer/78754 (Failure)`, you need to configure your Gmail account to allow less secure apps to access it: https://support.google.com/accounts/answer/6010255.
|
182
|
+
|
183
|
+
### Microsoft Graph configuration
|
184
|
+
|
185
|
+
To use the Microsoft Graph API instead of IMAP to read e-mail, you will
|
186
|
+
need to create an application in the Azure Active Directory. See the
|
187
|
+
[Microsoft instructions](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) for more details:
|
188
|
+
|
189
|
+
1. Sign in to the [Azure portal](https://portal.azure.com).
|
190
|
+
1. Search for and select `Azure Active Directory`.
|
191
|
+
1. Under `Manage`, select `App registrations` > `New registration`.
|
192
|
+
1. Enter a `Name` for your application, such as `MailRoom`. Users of your app might see this name, and you can change it later.
|
193
|
+
1. If `Supported account types` is listed, select the appropriate option.
|
194
|
+
1. Leave `Redirect URI` blank. This is not needed.
|
195
|
+
1. Select `Register`.
|
196
|
+
1. Under `Manage`, select `Certificates & secrets`.
|
197
|
+
1. Under `Client secrets`, select `New client secret`, and enter a name.
|
198
|
+
1. Under `Expires`, select `Never`, unless you plan on updating the credentials every time it expires.
|
199
|
+
1. Select `Add`. Record the secret value in a safe location for use in a later step.
|
200
|
+
1. Under `Manage`, select `API Permissions` > `Add a permission`. Select `Microsoft Graph`.
|
201
|
+
1. Select `Application permissions`.
|
202
|
+
1. Under the `Mail` node, select `Mail.ReadWrite`, and then select Add permissions.
|
203
|
+
1. If `User.Read` is listed in the permission list, you can delete this.
|
204
|
+
1. Click `Grant admin consent` for these permissions.
|
205
|
+
|
206
|
+
#### Restrict mailbox access
|
207
|
+
|
208
|
+
Note that for MailRoom to work as a service account, this application
|
209
|
+
must have the `Mail.ReadWrite` to read/write mail in *all*
|
210
|
+
mailboxes. However, while this appears to be security risk,
|
211
|
+
we can configure an application access policy to limit the
|
212
|
+
mailbox access for this account. [Follow these instructions](https://docs.microsoft.com/en-us/graph/auth-limit-mailbox-access)
|
213
|
+
to setup PowerShell and configure this policy.
|
214
|
+
|
215
|
+
#### MailRoom config for Microsoft Graph
|
216
|
+
|
217
|
+
In the MailRoom configuration, set `inbox_method` to `microsoft_graph`.
|
218
|
+
You will also need:
|
219
|
+
|
220
|
+
* The client and tenant ID from the `Overview` section in the Azure app page
|
221
|
+
* The client secret created earlier
|
222
|
+
|
223
|
+
Fill in `inbox_options` with these values:
|
224
|
+
|
225
|
+
```yaml
|
226
|
+
:inbox_method: microsoft_graph
|
227
|
+
:inbox_options:
|
228
|
+
:tenant_id: 12345
|
229
|
+
:client_id: ABCDE
|
230
|
+
:client_secret: YOUR-SECRET-HERE
|
231
|
+
:poll_interval: 60
|
232
|
+
```
|
233
|
+
|
234
|
+
By default, MailRoom will poll for new messages every 60 seconds. `poll_interval` configures the number of
|
235
|
+
seconds to poll. Setting the value to 0 or under will default to 60 seconds.
|
236
|
+
|
237
|
+
### Alternative Azure cloud deployments
|
238
|
+
|
239
|
+
MailRoom will default to using the standard Azure HTTPS endpoints. To
|
240
|
+
configure MailRoom with Microsoft Cloud for US Government or other
|
241
|
+
[national cloud deployments](https://docs.microsoft.com/en-us/graph/deployments), set
|
242
|
+
the `azure_ad_endpoint` and `graph_endpoint` accordingly. For example,
|
243
|
+
for Microsoft Cloud for US Government:
|
244
|
+
|
245
|
+
```yaml
|
246
|
+
:inbox_method: microsoft_graph
|
247
|
+
:inbox_options:
|
248
|
+
:tenant_id: 12345
|
249
|
+
:client_id: ABCDE
|
250
|
+
:client_secret: YOUR-SECRET-HERE
|
251
|
+
:poll_interval: 60
|
252
|
+
:azure_ad_endpoint: https://login.microsoftonline.us
|
253
|
+
:graph_endpoint: https://graph.microsoft.us
|
254
|
+
```
|
255
|
+
|
122
256
|
## delivery_method ##
|
123
257
|
|
124
258
|
### postback ###
|
@@ -263,20 +397,12 @@ it's probably because the content-type is set to Faraday's default, which is `H
|
|
263
397
|
## idle_timeout ##
|
264
398
|
|
265
399
|
By default, the IDLE command will wait for 29 minutes (in order to keep the server connection happy).
|
266
|
-
If you'd prefer not to wait that long, you can pass `
|
400
|
+
If you'd prefer not to wait that long, you can pass `idle_timeout` in seconds for your mailbox configuration.
|
267
401
|
|
268
402
|
## Search Command ##
|
269
403
|
|
270
404
|
This setting allows configuration of the IMAP search command sent to the server. This still defaults 'UNSEEN'. You may find that 'NEW' works better for you.
|
271
405
|
|
272
|
-
## IMAP Server Configuration ##
|
273
|
-
|
274
|
-
You can set per-mailbox configuration for the IMAP server's `host` (default: 'imap.gmail.com'), `port` (default: 993), `ssl` (default: true), and `start_tls` (default: false).
|
275
|
-
|
276
|
-
If you want to set additional options for IMAP SSL you can pass a YAML hash to match [SSLContext#set_params](http://docs.ruby-lang.org/en/2.2.0/OpenSSL/SSL/SSLContext.html#method-i-set_params). If you set `verify_mode` to `:none` it'll replace with the appropriate constant.
|
277
|
-
|
278
|
-
If you're seeing the error `Please log in via your web browser: https://support.google.com/mail/accounts/answer/78754 (Failure)`, you need to configure your Gmail account to allow less secure apps to access it: https://support.google.com/accounts/answer/6010255.
|
279
|
-
|
280
406
|
## Running in Production ##
|
281
407
|
|
282
408
|
I suggest running with either upstart or init.d. Check out this wiki page for some example scripts for both: https://github.com/tpitale/mail_room/wiki/Init-Scripts-for-Running-mail_room
|
data/Rakefile
CHANGED
@@ -31,7 +31,7 @@ module MailRoom
|
|
31
31
|
# Any subsequent failure in the instance which gets the lock will be dealt
|
32
32
|
# with by the expiration, at which time another instance can pick up the
|
33
33
|
# message and try again.
|
34
|
-
client.set(key, 1,
|
34
|
+
client.set(key, 1, nx: true, ex: expiration)
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
data/lib/mail_room/cli.rb
CHANGED
@@ -42,7 +42,7 @@ module MailRoom
|
|
42
42
|
end.parse!(args)
|
43
43
|
|
44
44
|
self.configuration = Configuration.new(options)
|
45
|
-
self.coordinator = Coordinator.new(configuration.mailboxes)
|
45
|
+
self.coordinator = Coordinator.new(configuration.mailboxes, configuration.health_check)
|
46
46
|
end
|
47
47
|
|
48
48
|
# Start the coordinator running, sets up signal traps
|
@@ -57,7 +57,7 @@ module MailRoom
|
|
57
57
|
|
58
58
|
coordinator.run
|
59
59
|
rescue Exception => e # not just Errors, but includes lower-level Exceptions
|
60
|
-
CrashHandler.new(
|
60
|
+
CrashHandler.new.handle(e, @options[:exit_error_format])
|
61
61
|
exit
|
62
62
|
end
|
63
63
|
end
|
@@ -4,7 +4,7 @@ module MailRoom
|
|
4
4
|
# Wraps configuration for a set of individual mailboxes with global config
|
5
5
|
# @author Tony Pitale
|
6
6
|
class Configuration
|
7
|
-
attr_accessor :mailboxes, :log_path, :quiet
|
7
|
+
attr_accessor :mailboxes, :log_path, :quiet, :health_check
|
8
8
|
|
9
9
|
# Initialize a new configuration of mailboxes
|
10
10
|
def initialize(options={})
|
@@ -18,6 +18,7 @@ module MailRoom
|
|
18
18
|
config_file = YAML.load(erb.result)
|
19
19
|
|
20
20
|
set_mailboxes(config_file[:mailboxes])
|
21
|
+
set_health_check(config_file[:health_check])
|
21
22
|
rescue => e
|
22
23
|
raise e unless quiet
|
23
24
|
end
|
@@ -32,5 +33,14 @@ module MailRoom
|
|
32
33
|
self.mailboxes << Mailbox.new(attributes)
|
33
34
|
end
|
34
35
|
end
|
36
|
+
|
37
|
+
# Builds the health checker from YAML configuration
|
38
|
+
#
|
39
|
+
# @param health_check_config nil or a Hash containing :address and :port
|
40
|
+
def set_health_check(health_check_config)
|
41
|
+
return unless health_check_config
|
42
|
+
|
43
|
+
self.health_check = HealthCheck.new(health_check_config)
|
44
|
+
end
|
35
45
|
end
|
36
46
|
end
|
data/lib/mail_room/connection.rb
CHANGED
@@ -1,195 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MailRoom
|
2
4
|
class Connection
|
5
|
+
attr_reader :mailbox, :new_message_handler
|
6
|
+
|
3
7
|
def initialize(mailbox)
|
4
8
|
@mailbox = mailbox
|
5
|
-
|
6
|
-
# log in and set the mailbox
|
7
|
-
reset
|
8
|
-
setup
|
9
|
+
@stopped = false
|
9
10
|
end
|
10
11
|
|
11
12
|
def on_new_message(&block)
|
12
13
|
@new_message_handler = block
|
13
14
|
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
def logged_in?
|
18
|
-
@logged_in
|
19
|
-
end
|
20
|
-
|
21
|
-
# is the connection blocked idling?
|
22
|
-
# @return [Boolean]
|
23
|
-
def idling?
|
24
|
-
@idling
|
25
|
-
end
|
26
|
-
|
27
|
-
# is the imap connection closed?
|
28
|
-
# @return [Boolean]
|
29
|
-
def disconnected?
|
30
|
-
@imap.disconnected?
|
31
|
-
end
|
32
|
-
|
33
|
-
# is the connection ready to idle?
|
34
|
-
# @return [Boolean]
|
35
|
-
def ready_to_idle?
|
36
|
-
logged_in? && !idling?
|
37
|
-
end
|
38
|
-
|
39
|
-
def quit
|
40
|
-
stop_idling
|
41
|
-
reset
|
16
|
+
def stopped?
|
17
|
+
@stopped
|
42
18
|
end
|
43
19
|
|
44
20
|
def wait
|
45
|
-
|
46
|
-
# in case we missed any between idles
|
47
|
-
process_mailbox
|
48
|
-
|
49
|
-
idle
|
50
|
-
|
51
|
-
process_mailbox
|
52
|
-
rescue Net::IMAP::Error, IOError
|
53
|
-
@mailbox.logger.warn({ context: @mailbox.context, action: "Disconnected. Resetting..." })
|
54
|
-
reset
|
55
|
-
setup
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def reset
|
62
|
-
@imap = nil
|
63
|
-
@logged_in = false
|
64
|
-
@idling = false
|
65
|
-
end
|
66
|
-
|
67
|
-
def setup
|
68
|
-
@mailbox.logger.info({ context: @mailbox.context, action: "Starting TLS session" })
|
69
|
-
start_tls
|
70
|
-
|
71
|
-
@mailbox.logger.info({ context: @mailbox.context, action: "Logging into mailbox" })
|
72
|
-
log_in
|
73
|
-
|
74
|
-
@mailbox.logger.info({ context: @mailbox.context, action: "Setting mailbox" })
|
75
|
-
set_mailbox
|
76
|
-
end
|
77
|
-
|
78
|
-
# build a net/imap connection to google imap
|
79
|
-
def imap
|
80
|
-
@imap ||= Net::IMAP.new(@mailbox.host, :port => @mailbox.port, :ssl => @mailbox.ssl_options)
|
81
|
-
end
|
82
|
-
|
83
|
-
# start a TLS session
|
84
|
-
def start_tls
|
85
|
-
imap.starttls if @mailbox.start_tls
|
86
|
-
end
|
87
|
-
|
88
|
-
# send the imap login command to google
|
89
|
-
def log_in
|
90
|
-
imap.login(@mailbox.email, @mailbox.password)
|
91
|
-
@logged_in = true
|
92
|
-
end
|
93
|
-
|
94
|
-
# select the mailbox name we want to use
|
95
|
-
def set_mailbox
|
96
|
-
imap.select(@mailbox.name) if logged_in?
|
97
|
-
end
|
98
|
-
|
99
|
-
# is the response for a new message?
|
100
|
-
# @param response [Net::IMAP::TaggedResponse] the imap response from idle
|
101
|
-
# @return [Boolean]
|
102
|
-
def message_exists?(response)
|
103
|
-
response.respond_to?(:name) && response.name == 'EXISTS'
|
104
|
-
end
|
105
|
-
|
106
|
-
# @private
|
107
|
-
def idle_handler
|
108
|
-
lambda {|response| imap.idle_done if message_exists?(response)}
|
109
|
-
end
|
110
|
-
|
111
|
-
# maintain an imap idle connection
|
112
|
-
def idle
|
113
|
-
return unless ready_to_idle?
|
114
|
-
|
115
|
-
@mailbox.logger.info({ context: @mailbox.context, action: "Idling" })
|
116
|
-
@idling = true
|
117
|
-
|
118
|
-
imap.idle(@mailbox.idle_timeout, &idle_handler)
|
119
|
-
ensure
|
120
|
-
@idling = false
|
121
|
-
end
|
122
|
-
|
123
|
-
# trigger the idle to finish and wait for the thread to finish
|
124
|
-
def stop_idling
|
125
|
-
return unless idling?
|
126
|
-
|
127
|
-
imap.idle_done
|
128
|
-
|
129
|
-
# idling_thread.join
|
130
|
-
# self.idling_thread = nil
|
131
|
-
end
|
132
|
-
|
133
|
-
def process_mailbox
|
134
|
-
return unless @new_message_handler
|
135
|
-
@mailbox.logger.info({ context: @mailbox.context, action: "Processing started" })
|
136
|
-
|
137
|
-
msgs = new_messages
|
138
|
-
|
139
|
-
any_deletions = msgs.
|
140
|
-
# deliver each new message, collect success
|
141
|
-
map(&@new_message_handler).
|
142
|
-
# include messages with success
|
143
|
-
zip(msgs).
|
144
|
-
# filter failed deliveries, collect message
|
145
|
-
select(&:first).map(&:last).
|
146
|
-
# scrub delivered messages
|
147
|
-
map { |message| scrub(message) }.
|
148
|
-
any?
|
149
|
-
|
150
|
-
imap.expunge if @mailbox.expunge_deleted && any_deletions
|
151
|
-
end
|
152
|
-
|
153
|
-
def scrub(message)
|
154
|
-
if @mailbox.delete_after_delivery
|
155
|
-
imap.store(message.seqno, "+FLAGS", [Net::IMAP::DELETED])
|
156
|
-
true
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
# @private
|
161
|
-
# fetch all messages for the new message ids
|
162
|
-
def new_messages
|
163
|
-
# Both of these calls may results in
|
164
|
-
# imap raising an EOFError, we handle
|
165
|
-
# this exception in the watcher
|
166
|
-
messages_for_ids(new_message_ids)
|
21
|
+
raise NotImplementedError
|
167
22
|
end
|
168
23
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
# @private
|
173
|
-
# search for all new (unseen) message ids
|
174
|
-
# @return [Array<Integer>] message ids
|
175
|
-
def new_message_ids
|
176
|
-
# uid_search still leaves messages UNSEEN
|
177
|
-
all_unread = @imap.uid_search(@mailbox.search_command)
|
178
|
-
|
179
|
-
to_deliver = all_unread.select { |uid| @mailbox.deliver?(uid) }
|
180
|
-
@mailbox.logger.info({ context: @mailbox.context, action: "Getting new messages", unread: {count: all_unread.count, ids: all_unread}, to_be_delivered: { count: to_deliver.count, ids: all_unread } })
|
181
|
-
to_deliver
|
182
|
-
end
|
183
|
-
|
184
|
-
# @private
|
185
|
-
# fetch the email for all given ids in RFC822 format
|
186
|
-
# @param ids [Array<Integer>] list of message ids
|
187
|
-
# @return [Array<Net::IMAP::FetchData>] the net/imap messages for the given ids
|
188
|
-
def messages_for_ids(uids)
|
189
|
-
return [] if uids.empty?
|
190
|
-
|
191
|
-
# uid_fetch marks as SEEN, will not be re-fetched for UNSEEN
|
192
|
-
imap.uid_fetch(uids, "RFC822")
|
24
|
+
def quit
|
25
|
+
@stopped = true
|
193
26
|
end
|
194
27
|
end
|
195
28
|
end
|
@@ -2,13 +2,15 @@ module MailRoom
|
|
2
2
|
# Coordinate the mailbox watchers
|
3
3
|
# @author Tony Pitale
|
4
4
|
class Coordinator
|
5
|
-
attr_accessor :watchers, :running
|
5
|
+
attr_accessor :watchers, :running, :health_check
|
6
6
|
|
7
7
|
# build watchers for a set of mailboxes
|
8
8
|
# @params mailboxes [Array<MailRoom::Mailbox>] mailboxes to be watched
|
9
|
-
|
9
|
+
# @params health_check <MailRoom::HealthCheck> health checker to run
|
10
|
+
def initialize(mailboxes, health_check = nil)
|
10
11
|
self.watchers = []
|
11
12
|
|
13
|
+
@health_check = health_check
|
12
14
|
mailboxes.each {|box| self.watchers << MailboxWatcher.new(box)}
|
13
15
|
end
|
14
16
|
|
@@ -16,10 +18,11 @@ module MailRoom
|
|
16
18
|
|
17
19
|
# start each of the watchers to running
|
18
20
|
def run
|
21
|
+
health_check&.run
|
19
22
|
watchers.each(&:run)
|
20
|
-
|
23
|
+
|
21
24
|
self.running = true
|
22
|
-
|
25
|
+
|
23
26
|
sleep_while_running
|
24
27
|
ensure
|
25
28
|
quit
|
@@ -27,6 +30,7 @@ module MailRoom
|
|
27
30
|
|
28
31
|
# quit each of the watchers when we're done running
|
29
32
|
def quit
|
33
|
+
health_check&.quit
|
30
34
|
watchers.each(&:quit)
|
31
35
|
end
|
32
36
|
|
@@ -1,30 +1,26 @@
|
|
1
|
+
require 'date'
|
1
2
|
|
2
3
|
module MailRoom
|
3
4
|
class CrashHandler
|
5
|
+
SUPPORTED_FORMATS = %w[json none]
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
SUPPORTED_FORMATS = %w[json plain]
|
8
|
-
|
9
|
-
def initialize(error:, format:)
|
10
|
-
@error = error
|
11
|
-
@format = format
|
7
|
+
def initialize(stream=STDOUT)
|
8
|
+
@stream = stream
|
12
9
|
end
|
13
10
|
|
14
|
-
def handle
|
11
|
+
def handle(error, format)
|
15
12
|
if format == 'json'
|
16
|
-
puts json
|
13
|
+
@stream.puts json(error)
|
17
14
|
return
|
18
15
|
end
|
19
16
|
|
20
|
-
# 'plain' is equivalent to outputting the error into stdout as-is
|
21
17
|
raise error
|
22
18
|
end
|
23
19
|
|
24
20
|
private
|
25
21
|
|
26
|
-
def json
|
27
|
-
{ time:
|
22
|
+
def json(error)
|
23
|
+
{ time: DateTime.now.iso8601(3), severity: :fatal, message: error.message, backtrace: error.backtrace }.to_json
|
28
24
|
end
|
29
25
|
end
|
30
26
|
end
|
@@ -24,7 +24,7 @@ module MailRoom
|
|
24
24
|
# Trigger `LetterOpener` to deliver our message
|
25
25
|
# @param message [String] the email message as a string, RFC822 format
|
26
26
|
def deliver(message)
|
27
|
-
method = ::LetterOpener::DeliveryMethod.new(:
|
27
|
+
method = ::LetterOpener::DeliveryMethod.new(location: @delivery_options.location)
|
28
28
|
method.deliver!(Mail.read_from_string(message))
|
29
29
|
|
30
30
|
true
|
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'faraday'
|
2
|
+
require "mail_room/jwt"
|
2
3
|
|
3
4
|
module MailRoom
|
4
5
|
module Delivery
|
5
6
|
# Postback Delivery method
|
6
7
|
# @author Tony Pitale
|
7
8
|
class Postback
|
8
|
-
Options = Struct.new(:url, :token, :username, :password, :logger, :content_type) do
|
9
|
+
Options = Struct.new(:url, :token, :username, :password, :logger, :content_type, :jwt) do
|
9
10
|
def initialize(mailbox)
|
10
11
|
url =
|
11
12
|
mailbox.delivery_url ||
|
@@ -17,6 +18,8 @@ module MailRoom
|
|
17
18
|
mailbox.delivery_options[:delivery_token] ||
|
18
19
|
mailbox.delivery_options[:token]
|
19
20
|
|
21
|
+
jwt = initialize_jwt(mailbox.delivery_options)
|
22
|
+
|
20
23
|
username = mailbox.delivery_options[:username]
|
21
24
|
password = mailbox.delivery_options[:password]
|
22
25
|
|
@@ -24,16 +27,31 @@ module MailRoom
|
|
24
27
|
|
25
28
|
content_type = mailbox.delivery_options[:content_type]
|
26
29
|
|
27
|
-
super(url, token, username, password, logger, content_type)
|
30
|
+
super(url, token, username, password, logger, content_type, jwt)
|
28
31
|
end
|
29
32
|
|
30
33
|
def token_auth?
|
31
34
|
!self[:token].nil?
|
32
35
|
end
|
33
36
|
|
37
|
+
def jwt_auth?
|
38
|
+
self[:jwt].valid?
|
39
|
+
end
|
40
|
+
|
34
41
|
def basic_auth?
|
35
42
|
!self[:username].nil? && !self[:password].nil?
|
36
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def initialize_jwt(delivery_options)
|
48
|
+
::MailRoom::JWT.new(
|
49
|
+
header: delivery_options[:jwt_auth_header],
|
50
|
+
secret_path: delivery_options[:jwt_secret_path],
|
51
|
+
algorithm: delivery_options[:jwt_algorithm],
|
52
|
+
issuer: delivery_options[:jwt_issuer]
|
53
|
+
)
|
54
|
+
end
|
37
55
|
end
|
38
56
|
|
39
57
|
# Build a new delivery, hold the delivery options
|
@@ -60,13 +78,27 @@ module MailRoom
|
|
60
78
|
connection.post do |request|
|
61
79
|
request.url @delivery_options.url
|
62
80
|
request.body = message
|
63
|
-
|
64
|
-
request
|
81
|
+
config_request_content_type(request)
|
82
|
+
config_request_jwt_auth(request)
|
65
83
|
end
|
66
84
|
|
67
85
|
@delivery_options.logger.info({ delivery_method: 'Postback', action: 'message pushed', url: @delivery_options.url })
|
68
86
|
true
|
69
87
|
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def config_request_content_type(request)
|
92
|
+
return if @delivery_options.content_type.nil?
|
93
|
+
|
94
|
+
request.headers['Content-Type'] = @delivery_options.content_type
|
95
|
+
end
|
96
|
+
|
97
|
+
def config_request_jwt_auth(request)
|
98
|
+
return unless @delivery_options.jwt_auth?
|
99
|
+
|
100
|
+
request.headers[@delivery_options.jwt.header] = @delivery_options.jwt.token
|
101
|
+
end
|
70
102
|
end
|
71
103
|
end
|
72
104
|
end
|
@@ -8,16 +8,17 @@ module MailRoom
|
|
8
8
|
# Sidekiq Delivery method
|
9
9
|
# @author Douwe Maan
|
10
10
|
class Sidekiq
|
11
|
-
Options = Struct.new(:redis_url, :namespace, :sentinels, :queue, :worker, :logger) do
|
11
|
+
Options = Struct.new(:redis_url, :namespace, :sentinels, :queue, :worker, :logger, :redis_db) do
|
12
12
|
def initialize(mailbox)
|
13
13
|
redis_url = mailbox.delivery_options[:redis_url] || "redis://localhost:6379"
|
14
|
+
redis_db = mailbox.delivery_options[:redis_db] || 0
|
14
15
|
namespace = mailbox.delivery_options[:namespace]
|
15
16
|
sentinels = mailbox.delivery_options[:sentinels]
|
16
17
|
queue = mailbox.delivery_options[:queue] || "default"
|
17
18
|
worker = mailbox.delivery_options[:worker]
|
18
19
|
logger = mailbox.logger
|
19
20
|
|
20
|
-
super(redis_url, namespace, sentinels, queue, worker, logger)
|
21
|
+
super(redis_url, namespace, sentinels, queue, worker, logger, redis_db)
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
@@ -45,7 +46,7 @@ module MailRoom
|
|
45
46
|
def client
|
46
47
|
@client ||= begin
|
47
48
|
sentinels = options.sentinels
|
48
|
-
redis_options = { url: options.redis_url }
|
49
|
+
redis_options = { url: options.redis_url, db: options.redis_db }
|
49
50
|
redis_options[:sentinels] = sentinels if sentinels
|
50
51
|
|
51
52
|
redis = ::Redis.new(redis_options)
|