mail_room 0.10.1 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +10 -0
- data/.github/workflows/ci.yml +54 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +86 -90
- data/.ruby-version +1 -1
- data/.tool-versions +1 -0
- data/CHANGELOG.md +8 -0
- data/README.md +40 -18
- data/lib/mail_room/arbitration/redis.rb +8 -1
- data/lib/mail_room/connection.rb +8 -1
- data/lib/mail_room/delivery/postback.rb +71 -11
- data/lib/mail_room/delivery/que.rb +15 -1
- data/lib/mail_room/delivery/sidekiq.rb +7 -0
- data/lib/mail_room/jwt.rb +39 -0
- data/lib/mail_room/microsoft_graph/connection.rb +33 -7
- data/lib/mail_room/version.rb +1 -1
- data/mail_room.gemspec +4 -3
- data/spec/fixtures/jwt_secret +1 -0
- data/spec/lib/arbitration/redis_spec.rb +6 -5
- data/spec/lib/delivery/letter_opener_spec.rb +2 -1
- data/spec/lib/delivery/logger_spec.rb +2 -1
- data/spec/lib/delivery/postback_spec.rb +51 -3
- data/spec/lib/delivery/sidekiq_spec.rb +7 -7
- data/spec/lib/jwt_spec.rb +80 -0
- data/spec/lib/microsoft_graph/connection_spec.rb +86 -24
- metadata +37 -10
- data/.travis.yml +0 -17
@@ -4,6 +4,7 @@ require 'mail_room/delivery/sidekiq'
|
|
4
4
|
describe MailRoom::Delivery::Sidekiq do
|
5
5
|
subject { described_class.new(options) }
|
6
6
|
let(:redis) { subject.send(:client) }
|
7
|
+
let(:raw_client) { redis._client }
|
7
8
|
let(:options) { MailRoom::Delivery::Sidekiq::Options.new(mailbox) }
|
8
9
|
|
9
10
|
describe '#options' do
|
@@ -20,7 +21,7 @@ describe MailRoom::Delivery::Sidekiq do
|
|
20
21
|
|
21
22
|
context 'with simple redis url' do
|
22
23
|
it 'client has same specified redis_url' do
|
23
|
-
expect(
|
24
|
+
expect(raw_client.options[:url]).to eq(redis_url)
|
24
25
|
end
|
25
26
|
|
26
27
|
it 'client is a instance of RedisNamespace class' do
|
@@ -39,10 +40,9 @@ describe MailRoom::Delivery::Sidekiq do
|
|
39
40
|
end
|
40
41
|
|
41
42
|
it 'client has correct redis_url' do
|
42
|
-
expect(
|
43
|
+
expect(raw_client.options[:url]).to eq(redis_url)
|
43
44
|
end
|
44
45
|
|
45
|
-
|
46
46
|
it 'connection has correct values' do
|
47
47
|
expect(redis.connection[:host]).to eq('localhost')
|
48
48
|
expect(redis.connection[:db]).to eq(4)
|
@@ -87,10 +87,10 @@ describe MailRoom::Delivery::Sidekiq do
|
|
87
87
|
before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
|
88
88
|
|
89
89
|
it 'client has same specified sentinel params' do
|
90
|
-
expect(
|
91
|
-
expect(
|
92
|
-
expect(
|
93
|
-
expect(
|
90
|
+
expect(raw_client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
|
91
|
+
expect(raw_client.options[:host]).to eq('sentinel-master')
|
92
|
+
expect(raw_client.options[:password]).to eq('mypassword')
|
93
|
+
expect(raw_client.options[:sentinels]).to eq(sentinels)
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'mail_room/jwt'
|
4
|
+
|
5
|
+
describe MailRoom::JWT do
|
6
|
+
let(:secret_path) { File.expand_path('../fixtures/jwt_secret', File.dirname(__FILE__)) }
|
7
|
+
let(:secret) { Base64.strict_decode64(File.read(secret_path).chomp) }
|
8
|
+
|
9
|
+
let(:standard_config) do
|
10
|
+
{
|
11
|
+
secret_path: secret_path,
|
12
|
+
issuer: 'mailroom',
|
13
|
+
header: 'Mailroom-Api-Request',
|
14
|
+
algorithm: 'HS256'
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#token' do
|
19
|
+
let(:jwt) { described_class.new(**standard_config) }
|
20
|
+
|
21
|
+
it 'generates a valid jwt token' do
|
22
|
+
token = jwt.token
|
23
|
+
expect(token).not_to be_empty
|
24
|
+
|
25
|
+
payload = nil
|
26
|
+
expect do
|
27
|
+
payload = JWT.decode(token, secret, true, iss: 'mailroom', verify_iat: true, verify_iss: true, algorithm: 'HS256')
|
28
|
+
end.not_to raise_error
|
29
|
+
expect(payload).to be_an(Array)
|
30
|
+
expect(payload).to match(
|
31
|
+
[
|
32
|
+
a_hash_including(
|
33
|
+
'iss' => 'mailroom',
|
34
|
+
'nonce' => be_a(String),
|
35
|
+
'iat' => be_a(Integer)
|
36
|
+
),
|
37
|
+
{ 'alg' => 'HS256' }
|
38
|
+
]
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'generates a different token for each invocation' do
|
43
|
+
expect(jwt.token).not_to eql(jwt.token)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#valid?' do
|
48
|
+
it 'returns true if all essential components are present' do
|
49
|
+
jwt = described_class.new(**standard_config)
|
50
|
+
expect(jwt.valid?).to eql(true)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns true if header and secret path are present' do
|
54
|
+
jwt = described_class.new(
|
55
|
+
secret_path: secret_path,
|
56
|
+
header: 'Mailroom-Api-Request',
|
57
|
+
issuer: nil,
|
58
|
+
algorithm: nil
|
59
|
+
)
|
60
|
+
expect(jwt.valid?).to eql(true)
|
61
|
+
expect(jwt.issuer).to eql(described_class::DEFAULT_ISSUER)
|
62
|
+
expect(jwt.algorithm).to eql(described_class::DEFAULT_ALGORITHM)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns false if either header or secret_path are missing' do
|
66
|
+
expect(described_class.new(
|
67
|
+
secret_path: nil,
|
68
|
+
header: 'Mailroom-Api-Request',
|
69
|
+
issuer: nil,
|
70
|
+
algorithm: nil
|
71
|
+
).valid?).to eql(false)
|
72
|
+
expect(described_class.new(
|
73
|
+
secret_path: secret_path,
|
74
|
+
header: nil,
|
75
|
+
issuer: nil,
|
76
|
+
algorithm: nil
|
77
|
+
).valid?).to eql(false)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -14,37 +14,58 @@ describe MailRoom::MicrosoftGraph::Connection do
|
|
14
14
|
}.merge(REQUIRED_MICROSOFT_GRAPH_DEFAULTS)
|
15
15
|
end
|
16
16
|
let(:mailbox) { build_mailbox(options) }
|
17
|
-
let(:
|
18
|
-
let(:
|
17
|
+
let(:graph_endpoint) { 'https://graph.microsoft.com' }
|
18
|
+
let(:azure_ad_endpoint) { 'https://login.microsoftonline.com' }
|
19
|
+
let(:base_url) { "#{graph_endpoint}/v1.0/users/user@example.com/mailFolders/inbox/messages" }
|
20
|
+
let(:message_base_url) { "#{graph_endpoint}/v1.0/users/user@example.com/messages" }
|
21
|
+
|
22
|
+
let(:connection) { described_class.new(mailbox) }
|
23
|
+
let(:uid) { 1 }
|
24
|
+
let(:access_token) { SecureRandom.hex }
|
25
|
+
let(:refresh_token) { SecureRandom.hex }
|
26
|
+
let(:expires_in) { Time.now + 3600 }
|
27
|
+
let(:unread_messages_body) { '' }
|
28
|
+
let(:status) { 200 }
|
29
|
+
let!(:stub_token) do
|
30
|
+
stub_request(:post, "#{azure_ad_endpoint}/#{tenant_id}/oauth2/v2.0/token").to_return(
|
31
|
+
body: { 'access_token' => access_token, 'refresh_token' => refresh_token, 'expires_in' => expires_in }.to_json,
|
32
|
+
headers: { 'Content-Type' => 'application/json' }
|
33
|
+
)
|
34
|
+
end
|
35
|
+
let!(:stub_unread_messages_request) do
|
36
|
+
stub_request(:get, "#{base_url}?$filter=isRead%20eq%20false").to_return(
|
37
|
+
status: status,
|
38
|
+
body: unread_messages_body.to_json,
|
39
|
+
headers: { 'Content-Type' => 'application/json' }
|
40
|
+
)
|
41
|
+
end
|
19
42
|
|
20
43
|
before do
|
21
44
|
WebMock.enable!
|
22
45
|
end
|
23
46
|
|
24
|
-
context '#
|
25
|
-
|
26
|
-
|
27
|
-
let(:access_token) { SecureRandom.hex }
|
28
|
-
let(:refresh_token) { SecureRandom.hex }
|
29
|
-
let(:expires_in) { Time.now + 3600 }
|
30
|
-
let(:unread_messages_body) { '' }
|
31
|
-
let(:status) { 200 }
|
32
|
-
let!(:stub_token) do
|
33
|
-
stub_request(:post, "https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token").to_return(
|
34
|
-
body: { 'access_token' => access_token, 'refresh_token' => refresh_token, 'expires_in' => expires_in }.to_json,
|
35
|
-
headers: { 'Content-Type' => 'application/json' }
|
36
|
-
)
|
47
|
+
context '#quit' do
|
48
|
+
it 'returns false' do
|
49
|
+
expect(connection.stopped?).to be_falsey
|
37
50
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
)
|
51
|
+
|
52
|
+
it 'returns true' do
|
53
|
+
connection.quit
|
54
|
+
|
55
|
+
expect(connection.stopped?).to be_truthy
|
44
56
|
end
|
45
57
|
|
58
|
+
it 'does not attempt to process the mailbox' do
|
59
|
+
connection.quit
|
60
|
+
|
61
|
+
connection.expects(:process_mailbox).times(0)
|
62
|
+
connection.wait
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context '#wait' do
|
46
67
|
before do
|
47
|
-
connection.stubs(:
|
68
|
+
connection.stubs(:do_sleep)
|
48
69
|
end
|
49
70
|
|
50
71
|
describe 'poll interval' do
|
@@ -52,6 +73,12 @@ describe MailRoom::MicrosoftGraph::Connection do
|
|
52
73
|
expect(connection.send(:poll_interval)).to eq(60)
|
53
74
|
end
|
54
75
|
|
76
|
+
it 'calls do_sleep 60 times' do
|
77
|
+
connection.expects(:do_sleep).with(1).times(60)
|
78
|
+
|
79
|
+
connection.wait
|
80
|
+
end
|
81
|
+
|
55
82
|
context 'interval set to 10' do
|
56
83
|
let(:options) do
|
57
84
|
{
|
@@ -68,10 +95,16 @@ describe MailRoom::MicrosoftGraph::Connection do
|
|
68
95
|
it 'sets the poll interval to 10' do
|
69
96
|
expect(connection.send(:poll_interval)).to eq(10)
|
70
97
|
end
|
98
|
+
|
99
|
+
it 'calls do_sleep 10 times' do
|
100
|
+
connection.expects(:do_sleep).with(1).times(10)
|
101
|
+
|
102
|
+
connection.wait
|
103
|
+
end
|
71
104
|
end
|
72
105
|
end
|
73
106
|
|
74
|
-
|
107
|
+
shared_examples 'with a single message' do
|
75
108
|
let(:message_id) { SecureRandom.hex }
|
76
109
|
let(:unread_messages_body) { { value: ['id' => message_id] } }
|
77
110
|
let(:message_url) { "#{message_base_url}/#{message_id}" }
|
@@ -103,9 +136,38 @@ describe MailRoom::MicrosoftGraph::Connection do
|
|
103
136
|
end
|
104
137
|
end
|
105
138
|
|
139
|
+
context 'with default Azure settings' do
|
140
|
+
before do
|
141
|
+
puts options
|
142
|
+
end
|
143
|
+
it_behaves_like 'with a single message'
|
144
|
+
end
|
145
|
+
|
146
|
+
# https://docs.microsoft.com/en-us/graph/deployments
|
147
|
+
context 'with an alternative Azure deployment' do
|
148
|
+
let(:graph_endpoint) { 'https://graph.microsoft.us' }
|
149
|
+
let(:azure_ad_endpoint) { 'https://login.microsoftonline.us' }
|
150
|
+
let(:options) do
|
151
|
+
{
|
152
|
+
inbox_method: :microsoft_graph,
|
153
|
+
delete_after_delivery: true,
|
154
|
+
expunge_deleted: true,
|
155
|
+
inbox_options: {
|
156
|
+
tenant_id: '98776',
|
157
|
+
client_id: '12345',
|
158
|
+
client_secret: 'MY-SECRET',
|
159
|
+
graph_endpoint: 'https://graph.microsoft.us',
|
160
|
+
azure_ad_endpoint: 'https://login.microsoftonline.us'
|
161
|
+
}
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
it_behaves_like 'with a single message'
|
166
|
+
end
|
167
|
+
|
106
168
|
context 'with multiple pages of messages' do
|
107
169
|
let(:message_ids) { [SecureRandom.hex, SecureRandom.hex] }
|
108
|
-
let(:next_page_url) {
|
170
|
+
let(:next_page_url) { "#{graph_endpoint}/v1.0/nextPage" }
|
109
171
|
let(:unread_messages_body) { { value: ['id' => message_ids.first], '@odata.nextLink' => next_page_url } }
|
110
172
|
let(:message_body) { 'hello world' }
|
111
173
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mail_room
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Pitale
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-imap
|
@@ -28,16 +28,36 @@ dependencies:
|
|
28
28
|
name: oauth2
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 1.4.4
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '3'
|
34
37
|
type: :runtime
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
|
-
- - "
|
41
|
+
- - ">="
|
39
42
|
- !ruby/object:Gem::Version
|
40
43
|
version: 1.4.4
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: jwt
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '2.0'
|
41
61
|
- !ruby/object:Gem::Dependency
|
42
62
|
name: rake
|
43
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,14 +106,14 @@ dependencies:
|
|
86
106
|
requirements:
|
87
107
|
- - "~>"
|
88
108
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
109
|
+
version: '2.0'
|
90
110
|
type: :development
|
91
111
|
prerelease: false
|
92
112
|
version_requirements: !ruby/object:Gem::Requirement
|
93
113
|
requirements:
|
94
114
|
- - "~>"
|
95
115
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
116
|
+
version: '2.0'
|
97
117
|
- !ruby/object:Gem::Dependency
|
98
118
|
name: simplecov
|
99
119
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,14 +176,14 @@ dependencies:
|
|
156
176
|
requirements:
|
157
177
|
- - "~>"
|
158
178
|
- !ruby/object:Gem::Version
|
159
|
-
version:
|
179
|
+
version: '4'
|
160
180
|
type: :development
|
161
181
|
prerelease: false
|
162
182
|
version_requirements: !ruby/object:Gem::Requirement
|
163
183
|
requirements:
|
164
184
|
- - "~>"
|
165
185
|
- !ruby/object:Gem::Version
|
166
|
-
version:
|
186
|
+
version: '4'
|
167
187
|
- !ruby/object:Gem::Dependency
|
168
188
|
name: redis-namespace
|
169
189
|
requirement: !ruby/object:Gem::Requirement
|
@@ -228,12 +248,14 @@ executables:
|
|
228
248
|
extensions: []
|
229
249
|
extra_rdoc_files: []
|
230
250
|
files:
|
251
|
+
- ".github/dependabot.yml"
|
252
|
+
- ".github/workflows/ci.yml"
|
231
253
|
- ".gitignore"
|
232
254
|
- ".gitlab-ci.yml"
|
233
255
|
- ".rubocop.yml"
|
234
256
|
- ".rubocop_todo.yml"
|
235
257
|
- ".ruby-version"
|
236
|
-
- ".
|
258
|
+
- ".tool-versions"
|
237
259
|
- CHANGELOG.md
|
238
260
|
- CODE_OF_CONDUCT.md
|
239
261
|
- Gemfile
|
@@ -260,6 +282,7 @@ files:
|
|
260
282
|
- lib/mail_room/imap.rb
|
261
283
|
- lib/mail_room/imap/connection.rb
|
262
284
|
- lib/mail_room/imap/message.rb
|
285
|
+
- lib/mail_room/jwt.rb
|
263
286
|
- lib/mail_room/logger/structured.rb
|
264
287
|
- lib/mail_room/mailbox.rb
|
265
288
|
- lib/mail_room/mailbox_watcher.rb
|
@@ -269,6 +292,7 @@ files:
|
|
269
292
|
- lib/mail_room/version.rb
|
270
293
|
- logfile.log
|
271
294
|
- mail_room.gemspec
|
295
|
+
- spec/fixtures/jwt_secret
|
272
296
|
- spec/fixtures/test_config.yml
|
273
297
|
- spec/lib/arbitration/redis_spec.rb
|
274
298
|
- spec/lib/cli_spec.rb
|
@@ -282,6 +306,7 @@ files:
|
|
282
306
|
- spec/lib/delivery/sidekiq_spec.rb
|
283
307
|
- spec/lib/imap/connection_spec.rb
|
284
308
|
- spec/lib/imap/message_spec.rb
|
309
|
+
- spec/lib/jwt_spec.rb
|
285
310
|
- spec/lib/logger/structured_spec.rb
|
286
311
|
- spec/lib/mailbox_spec.rb
|
287
312
|
- spec/lib/mailbox_watcher_spec.rb
|
@@ -306,12 +331,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
306
331
|
- !ruby/object:Gem::Version
|
307
332
|
version: '0'
|
308
333
|
requirements: []
|
309
|
-
rubygems_version: 3.
|
334
|
+
rubygems_version: 3.5.5
|
310
335
|
signing_key:
|
311
336
|
specification_version: 4
|
312
337
|
summary: mail_room will proxy email (gmail) from IMAP to a callback URL, logger, or
|
313
338
|
letter_opener
|
314
339
|
test_files:
|
340
|
+
- spec/fixtures/jwt_secret
|
315
341
|
- spec/fixtures/test_config.yml
|
316
342
|
- spec/lib/arbitration/redis_spec.rb
|
317
343
|
- spec/lib/cli_spec.rb
|
@@ -325,6 +351,7 @@ test_files:
|
|
325
351
|
- spec/lib/delivery/sidekiq_spec.rb
|
326
352
|
- spec/lib/imap/connection_spec.rb
|
327
353
|
- spec/lib/imap/message_spec.rb
|
354
|
+
- spec/lib/jwt_spec.rb
|
328
355
|
- spec/lib/logger/structured_spec.rb
|
329
356
|
- spec/lib/mailbox_spec.rb
|
330
357
|
- spec/lib/mailbox_watcher_spec.rb
|