gitlab-mail_room 0.0.15 → 0.0.19

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec4ee38465229ec567497afa371c9001ba6b55f3f7b427815d2aeeaaa3916569
4
- data.tar.gz: f98d83bd07c436f6d833c8ed9710a7cfe33d43a0f56891495eb495c75001ac6f
3
+ metadata.gz: 2e9602ef5d0f53b51c947485678b32f45b0d6099a2b9e10dea153358bc5023c4
4
+ data.tar.gz: 05203d73f36e129d0f463537d2be619181c804a60aacbdf58d6a12ca6dbf8392
5
5
  SHA512:
6
- metadata.gz: e7146b00f46e4ba842fad2234caf4f9145263f25aa4b21daaf16b17d5308d9cb7fc028c4cdaeede5e542bc05476bbbf95dff3f9fc5aae35fefca458004cfcd6e
7
- data.tar.gz: c89588a3d43583ce20f1513b8488293f8b2f56b13bd15331276288f4331f2bef59abef77d790836cdcfe298d8af3fc9c4475586c44bb174fb485a6e1f4ac0834
6
+ metadata.gz: b2860a2390178f2862d0ac3f139f1330e9502cfab0ea06d850feef7212c6575c0b97eddda0b958ad274038a819261d1e2984997a52b7e54e6c6b9d5707f6c51c
7
+ data.tar.gz: 5b5f63868bfee8235d4be4f56e62d5d83be90ad3faa8dfa99776720176133de725defb143757058eccd32daee8ef75505560ee91523cc27c0876f3b2a60941cb
data/.gitlab-ci.yml CHANGED
@@ -1,4 +1,5 @@
1
- # Cache gems in between builds
1
+ default:
2
+ image: "ruby:${RUBY_VERSION}"
2
3
 
3
4
  services:
4
5
  - redis:latest
@@ -9,24 +10,17 @@ services:
9
10
  - vendor/ruby
10
11
  variables:
11
12
  REDIS_URL: redis://redis:6379
12
- script:
13
- - bundle exec rspec spec
14
13
  before_script:
15
14
  - apt update && apt install -y libicu-dev
16
15
  - ruby -v # Print out ruby version for debugging
17
- # Uncomment next line if your rails app needs a JS runtime:
18
- # - apt-get update -q && apt-get install nodejs -yqq
19
- - gem install bundler --no-document # Bundler is not installed with the image
20
- - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby
21
-
22
- rspec-2.6:
23
- image: "ruby:2.6"
24
- <<: *test
25
-
26
- rspec-2.7:
27
- image: "ruby:2.7"
28
- <<: *test
16
+ - gem install bundler --no-document # Bundler is not installed with the image
17
+ - bundle config set --local path 'vendor'
18
+ - bundle install -j $(nproc)
19
+ script:
20
+ - bundle exec rspec spec
29
21
 
30
- rspec-3.0:
31
- image: "ruby:3.0"
22
+ rspec:
23
+ parallel:
24
+ matrix:
25
+ - RUBY_VERSION: [ "2.5", "2.6", "2.7", "3.0" ]
32
26
  <<: *test
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.7.2
1
+ 2.7.5
data/README.md CHANGED
@@ -138,6 +138,17 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
138
138
  :delivery_options:
139
139
  :redis_url: redis://localhost:6379
140
140
  :worker: EmailReceiverWorker
141
+ -
142
+ :email: "user8@gmail.com"
143
+ :password: "password"
144
+ :name: "inbox"
145
+ :delivery_method: postback
146
+ :delivery_options:
147
+ :delivery_url: "http://localhost:3000/inbox"
148
+ :jwt_auth_header: "Mailroom-Api-Request"
149
+ :jwt_issuer: "mailroom"
150
+ :jwt_algorithm: "HS256"
151
+ :jwt_secret_path: "/etc/secrets/mailroom/.mailroom_secret"
141
152
  ```
142
153
 
143
154
  **Note:** If using `delete_after_delivery`, you also probably want to use
@@ -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, {nx: true, ex: expiration})
34
+ client.set(key, 1, nx: true, ex: expiration)
35
35
  end
36
36
 
37
37
  private
@@ -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
- # request.options[:timeout] = 3
64
- request.headers['Content-Type'] = @delivery_options.content_type unless @delivery_options.content_type.nil?
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
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'securerandom'
5
+ require 'jwt'
6
+ require 'base64'
7
+
8
+ module MailRoom
9
+ # Responsible for validating and generating JWT token
10
+ class JWT
11
+ DEFAULT_ISSUER = 'mailroom'
12
+ DEFAULT_ALGORITHM = 'HS256'
13
+
14
+ attr_reader :header, :secret_path, :issuer, :algorithm
15
+
16
+ def initialize(header:, secret_path:, issuer:, algorithm:)
17
+ @header = header
18
+ @secret_path = secret_path
19
+ @issuer = issuer || DEFAULT_ISSUER
20
+ @algorithm = algorithm || DEFAULT_ALGORITHM
21
+ end
22
+
23
+ def valid?
24
+ [@header, @secret_path, @issuer, @algorithm].none?(&:nil?)
25
+ end
26
+
27
+ def token
28
+ return nil unless valid?
29
+
30
+ secret = Base64.strict_decode64(File.read(@secret_path).chomp)
31
+ payload = {
32
+ nonce: SecureRandom.hex(12),
33
+ iat: Time.now.to_i, # https://github.com/jwt/ruby-jwt#issued-at-claim
34
+ iss: @issuer
35
+ }
36
+ ::JWT.encode payload, secret, @algorithm
37
+ end
38
+ end
39
+ end
@@ -1,4 +1,4 @@
1
1
  module MailRoom
2
2
  # Current version of gitlab-mail_room gem
3
- VERSION = "0.0.15"
3
+ VERSION = "0.0.19"
4
4
  end
data/mail_room.gemspec CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_dependency "net-imap", ">= 0.2.1"
21
21
  gem.add_dependency "oauth2", "~> 1.4.4"
22
+ gem.add_dependency "jwt", ">= 2.0"
22
23
 
23
24
  # Pinning io-wait to 0.1.0, which is the last version to support Ruby < 3
24
25
  gem.add_dependency "io-wait", "~> 0.1.0"
@@ -34,7 +35,7 @@ Gem::Specification.new do |gem|
34
35
  gem.add_development_dependency "faraday"
35
36
  gem.add_development_dependency "mail"
36
37
  gem.add_development_dependency "letter_opener"
37
- gem.add_development_dependency "redis", "~> 3.3.1"
38
+ gem.add_development_dependency "redis", "~> 4"
38
39
  gem.add_development_dependency "redis-namespace"
39
40
  gem.add_development_dependency "pg"
40
41
  gem.add_development_dependency "charlock_holmes"
@@ -0,0 +1 @@
1
+ aGVsbG93b3JsZA==
@@ -15,6 +15,7 @@ describe MailRoom::Arbitration::Redis do
15
15
 
16
16
  # Private, but we don't care.
17
17
  let(:redis) { subject.send(:client) }
18
+ let(:raw_client) { redis._client }
18
19
 
19
20
  describe '#deliver?' do
20
21
  context "when called the first time" do
@@ -95,7 +96,7 @@ describe MailRoom::Arbitration::Redis do
95
96
  it 'client has same specified url' do
96
97
  subject.deliver?(123)
97
98
 
98
- expect(redis.client.options[:url]).to eq redis_url
99
+ expect(raw_client.options[:url]).to eq redis_url
99
100
  end
100
101
 
101
102
  it 'client is a instance of Redis class' do
@@ -137,10 +138,10 @@ describe MailRoom::Arbitration::Redis do
137
138
  before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
138
139
 
139
140
  it 'client has same specified sentinel params' do
140
- expect(redis.client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
141
- expect(redis.client.options[:host]).to eq('sentinel-master')
142
- expect(redis.client.options[:password]).to eq('mypassword')
143
- expect(redis.client.options[:sentinels]).to eq(sentinels)
141
+ expect(raw_client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
142
+ expect(raw_client.options[:host]).to eq('sentinel-master')
143
+ expect(raw_client.options[:password]).to eq('mypassword')
144
+ expect(raw_client.options[:sentinels]).to eq(sentinels)
144
145
  end
145
146
  end
146
147
  end
@@ -65,11 +65,10 @@ describe MailRoom::Delivery::Postback do
65
65
  }
66
66
  })}
67
67
 
68
-
69
68
  let(:delivery_options) {
70
69
  MailRoom::Delivery::Postback::Options.new(mailbox)
71
70
  }
72
-
71
+
73
72
  it 'posts the message with faraday' do
74
73
  connection = stub
75
74
  request = stub
@@ -82,10 +81,59 @@ describe MailRoom::Delivery::Postback do
82
81
  connection.expects(:basic_auth).with('user1', 'password123abc')
83
82
 
84
83
  MailRoom::Delivery::Postback.new(delivery_options).deliver('a message')
85
-
84
+
86
85
  expect(request.headers['Content-Type']).to eq('text/plain')
87
86
  end
88
87
  end
88
+
89
+ context 'with jwt token in the delivery options' do
90
+ let(:mailbox) {build_mailbox({
91
+ delivery_options: {
92
+ url: 'http://localhost/inbox',
93
+ jwt_auth_header: "Mailroom-Api-Request",
94
+ jwt_issuer: "mailroom",
95
+ jwt_algorithm: "HS256",
96
+ jwt_secret_path: "secret_path"
97
+ }
98
+ })}
99
+
100
+ let(:delivery_options) {
101
+ MailRoom::Delivery::Postback::Options.new(mailbox)
102
+ }
103
+
104
+ it 'posts the message with faraday' do
105
+ connection = stub
106
+ request = stub
107
+ Faraday.stubs(:new).returns(connection)
108
+
109
+ connection.expects(:post).yields(request).twice
110
+ request.stubs(:url)
111
+ request.stubs(:body=)
112
+ request.stubs(:headers).returns({})
113
+
114
+ jwt = stub
115
+ MailRoom::JWT.expects(:new).with(
116
+ header: 'Mailroom-Api-Request',
117
+ issuer: 'mailroom',
118
+ algorithm: 'HS256',
119
+ secret_path: 'secret_path'
120
+ ).returns(jwt)
121
+ jwt.stubs(:valid?).returns(true)
122
+ jwt.stubs(:header).returns('Mailroom-Api-Request')
123
+ jwt.stubs(:token).returns('a_jwt_token')
124
+
125
+ delivery = MailRoom::Delivery::Postback.new(delivery_options)
126
+
127
+ delivery.deliver('a message')
128
+ expect(request.headers['Mailroom-Api-Request']).to eql('a_jwt_token')
129
+
130
+ # A different jwt token for the second time
131
+ jwt.stubs(:token).returns('another_jwt_token')
132
+
133
+ delivery.deliver('another message')
134
+ expect(request.headers['Mailroom-Api-Request']).to eql('another_jwt_token')
135
+ end
136
+ end
89
137
  end
90
138
  end
91
139
  end
@@ -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(redis.client.options[:url]).to eq(redis_url)
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,7 +40,7 @@ describe MailRoom::Delivery::Sidekiq do
39
40
  end
40
41
 
41
42
  it 'client has correct redis_url' do
42
- expect(redis.client.options[:url]).to eq(redis_url)
43
+ expect(raw_client.options[:url]).to eq(redis_url)
43
44
  end
44
45
 
45
46
 
@@ -87,10 +88,10 @@ describe MailRoom::Delivery::Sidekiq do
87
88
  before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
88
89
 
89
90
  it 'client has same specified sentinel params' do
90
- expect(redis.client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
91
- expect(redis.client.options[:host]).to eq('sentinel-master')
92
- expect(redis.client.options[:password]).to eq('mypassword')
93
- expect(redis.client.options[:sentinels]).to eq(sentinels)
91
+ expect(raw_client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
92
+ expect(raw_client.options[:host]).to eq('sentinel-master')
93
+ expect(raw_client.options[:password]).to eq('mypassword')
94
+ expect(raw_client.options[:sentinels]).to eq(sentinels)
94
95
  end
95
96
  end
96
97
 
@@ -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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-mail_room
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.15
4
+ version: 0.0.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Pitale
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-14 00:00:00.000000000 Z
11
+ date: 2022-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-imap
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.4.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: jwt
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: io-wait
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -184,14 +198,14 @@ dependencies:
184
198
  requirements:
185
199
  - - "~>"
186
200
  - !ruby/object:Gem::Version
187
- version: 3.3.1
201
+ version: '4'
188
202
  type: :development
189
203
  prerelease: false
190
204
  version_requirements: !ruby/object:Gem::Requirement
191
205
  requirements:
192
206
  - - "~>"
193
207
  - !ruby/object:Gem::Version
194
- version: 3.3.1
208
+ version: '4'
195
209
  - !ruby/object:Gem::Dependency
196
210
  name: redis-namespace
197
211
  requirement: !ruby/object:Gem::Requirement
@@ -291,6 +305,7 @@ files:
291
305
  - lib/mail_room/imap.rb
292
306
  - lib/mail_room/imap/connection.rb
293
307
  - lib/mail_room/imap/message.rb
308
+ - lib/mail_room/jwt.rb
294
309
  - lib/mail_room/logger/structured.rb
295
310
  - lib/mail_room/mailbox.rb
296
311
  - lib/mail_room/mailbox_watcher.rb
@@ -300,6 +315,7 @@ files:
300
315
  - lib/mail_room/version.rb
301
316
  - logfile.log
302
317
  - mail_room.gemspec
318
+ - spec/fixtures/jwt_secret
303
319
  - spec/fixtures/test_config.yml
304
320
  - spec/lib/arbitration/redis_spec.rb
305
321
  - spec/lib/cli_spec.rb
@@ -314,6 +330,7 @@ files:
314
330
  - spec/lib/health_check_spec.rb
315
331
  - spec/lib/imap/connection_spec.rb
316
332
  - spec/lib/imap/message_spec.rb
333
+ - spec/lib/jwt_spec.rb
317
334
  - spec/lib/logger/structured_spec.rb
318
335
  - spec/lib/mailbox_spec.rb
319
336
  - spec/lib/mailbox_watcher_spec.rb
@@ -338,12 +355,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
338
355
  - !ruby/object:Gem::Version
339
356
  version: '0'
340
357
  requirements: []
341
- rubygems_version: 3.1.4
358
+ rubygems_version: 3.3.5
342
359
  signing_key:
343
360
  specification_version: 4
344
361
  summary: mail_room will proxy email (gmail) from IMAP to a callback URL, logger, or
345
362
  letter_opener
346
363
  test_files:
364
+ - spec/fixtures/jwt_secret
347
365
  - spec/fixtures/test_config.yml
348
366
  - spec/lib/arbitration/redis_spec.rb
349
367
  - spec/lib/cli_spec.rb
@@ -358,6 +376,7 @@ test_files:
358
376
  - spec/lib/health_check_spec.rb
359
377
  - spec/lib/imap/connection_spec.rb
360
378
  - spec/lib/imap/message_spec.rb
379
+ - spec/lib/jwt_spec.rb
361
380
  - spec/lib/logger/structured_spec.rb
362
381
  - spec/lib/mailbox_spec.rb
363
382
  - spec/lib/mailbox_watcher_spec.rb