sendgrid-ruby 6.1.4 → 6.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -1
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +11 -20
  6. data/CHANGELOG.md +45 -0
  7. data/CONTRIBUTING.md +4 -11
  8. data/Dockerfile +14 -0
  9. data/Gemfile +0 -1
  10. data/Makefile +9 -2
  11. data/README.md +0 -1
  12. data/examples/helpers/eventwebhook/example.rb +16 -0
  13. data/lib/rack/sendgrid_webhook_verification.rb +52 -0
  14. data/lib/sendgrid-ruby.rb +5 -1
  15. data/lib/sendgrid/base_interface.rb +36 -0
  16. data/lib/sendgrid/helpers/eventwebhook/eventwebhook.rb +52 -0
  17. data/lib/sendgrid/sendgrid.rb +20 -0
  18. data/lib/sendgrid/twilio_email.rb +21 -0
  19. data/lib/sendgrid/version.rb +1 -1
  20. data/sendgrid-ruby.gemspec +2 -0
  21. data/spec/fixtures/event_webhook.rb +16 -0
  22. data/spec/rack/sendgrid_webhook_verification_spec.rb +116 -0
  23. data/spec/sendgrid/helpers/eventwebhook/eventwebhook_spec.rb +103 -0
  24. data/spec/sendgrid/sendgrid_spec.rb +11 -0
  25. data/spec/sendgrid/twilio_email_spec.rb +11 -0
  26. data/spec/spec_helper.rb +2 -0
  27. data/test/sendgrid/helpers/mail/test_mail.rb +1 -1
  28. data/test/sendgrid/test_sendgrid-ruby.rb +24 -59
  29. data/use-cases/README.md +16 -0
  30. data/use-cases/domain-authentication.md +5 -0
  31. data/use-cases/email-statistics.md +52 -0
  32. data/use-cases/legacy-templates.md +98 -0
  33. data/use-cases/sms.md +39 -0
  34. data/use-cases/transactional-templates.md +111 -0
  35. data/use-cases/twilio-email.md +13 -0
  36. data/use-cases/twilio-setup.md +54 -0
  37. metadata +57 -9
  38. data/USE_CASES.md +0 -377
  39. data/docker/Dockerfile +0 -12
  40. data/docker/README.md +0 -30
  41. data/lib/sendgrid/client.rb +0 -38
  42. data/test/prism.sh +0 -42
@@ -0,0 +1,16 @@
1
+ require "json"
2
+
3
+ module Fixtures
4
+ module EventWebhook
5
+ PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA=='
6
+ FAILING_PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqTxd43gyp8IOEto2LdIfjRQrIbsd4SXZkLW6jDutdhXSJCWHw8REntlo7aNDthvj+y7GjUuFDb/R1NGe1OPzpA=='
7
+ SIGNATURE = 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0='
8
+ FAILING_SIGNATURE = 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH3j/0='
9
+ TIMESTAMP = '1588788367'
10
+ PAYLOAD = {
11
+ 'category'=>'example_payload',
12
+ 'event'=>'test_event',
13
+ 'message_id'=>'message_id',
14
+ }.to_json
15
+ end
16
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+ require 'rack/mock'
3
+ require './spec/fixtures/event_webhook'
4
+
5
+ unless RUBY_PLATFORM == 'java'
6
+ describe Rack::SendGridWebhookVerification do
7
+ let(:public_key) { Fixtures::EventWebhook::PUBLIC_KEY }
8
+ before do
9
+ @app = ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['Hello']] }
10
+ end
11
+
12
+ describe 'new' do
13
+ it 'should initialize with an app, public key and a path' do
14
+ expect do
15
+ Rack::SendGridWebhookVerification.new(@app, 'ABC', /\/email/)
16
+ end.not_to raise_error
17
+ end
18
+
19
+ it 'should initialize with an app, public key and paths' do
20
+ expect do
21
+ Rack::SendGridWebhookVerification.new(@app, 'ABC', /\/email/, /\/event/)
22
+ end.not_to raise_error
23
+ end
24
+ end
25
+
26
+ describe 'calling against one path' do
27
+ let(:middleware) { Rack::SendGridWebhookVerification.new(@app, public_key, /\/email/) }
28
+
29
+ it "should not intercept when the path doesn't match" do
30
+ expect(SendGrid::EventWebhook).to_not receive(:new)
31
+ request = Rack::MockRequest.env_for('/login')
32
+ status, headers, body = middleware.call(request)
33
+ expect(status).to eq(200)
34
+ end
35
+
36
+ it 'should allow a request through if it is verified' do
37
+ options = {
38
+ :input => Fixtures::EventWebhook::PAYLOAD,
39
+ 'Content-Type' => "application/json"
40
+ }
41
+ options[SendGrid::EventWebhookHeader::SIGNATURE] = Fixtures::EventWebhook::SIGNATURE
42
+ options[SendGrid::EventWebhookHeader::TIMESTAMP] = Fixtures::EventWebhook::TIMESTAMP
43
+ request = Rack::MockRequest.env_for('/email', options)
44
+ status, headers, body = middleware.call(request)
45
+ expect(status).to eq(200)
46
+ end
47
+
48
+ it 'should short circuit a request to 403 if there is no signature or timestamp' do
49
+ options = {
50
+ :input => Fixtures::EventWebhook::PAYLOAD,
51
+ 'Content-Type' => "application/json"
52
+ }
53
+ request = Rack::MockRequest.env_for('/email', options)
54
+ status, headers, body = middleware.call(request)
55
+ expect(status).to eq(403)
56
+ end
57
+
58
+ it 'should short circuit a request to 403 if the signature is incorrect' do
59
+ options = {
60
+ :input => Fixtures::EventWebhook::PAYLOAD,
61
+ 'Content-Type' => "application/json"
62
+ }
63
+ options[SendGrid::EventWebhookHeader::SIGNATURE] = Fixtures::EventWebhook::FAILING_SIGNATURE
64
+ options[SendGrid::EventWebhookHeader::TIMESTAMP] = Fixtures::EventWebhook::TIMESTAMP
65
+ request = Rack::MockRequest.env_for('/email', options)
66
+ status, headers, body = middleware.call(request)
67
+ expect(status).to eq(403)
68
+ end
69
+
70
+ it 'should short circuit a request to 403 if the payload is incorrect' do
71
+ options = {
72
+ :input => 'payload',
73
+ 'Content-Type' => "application/json"
74
+ }
75
+ options[SendGrid::EventWebhookHeader::SIGNATURE] = Fixtures::EventWebhook::SIGNATURE
76
+ options[SendGrid::EventWebhookHeader::TIMESTAMP] = Fixtures::EventWebhook::TIMESTAMP
77
+ request = Rack::MockRequest.env_for('/email', options)
78
+ status, headers, body = middleware.call(request)
79
+ expect(status).to eq(403)
80
+ end
81
+ end
82
+
83
+ describe 'calling with multiple paths' do
84
+ let(:middleware) { Rack::SendGridWebhookVerification.new(@app, public_key, /\/email/, /\/events/) }
85
+
86
+ it "should not intercept when the path doesn't match" do
87
+ expect(SendGrid::EventWebhook).to_not receive(:new)
88
+ request = Rack::MockRequest.env_for('/sms_events')
89
+ status, headers, body = middleware.call(request)
90
+ expect(status).to eq(200)
91
+ end
92
+
93
+ it 'should allow a request through if it is verified' do
94
+ options = {
95
+ :input => Fixtures::EventWebhook::PAYLOAD,
96
+ 'Content-Type' => "application/json"
97
+ }
98
+ options[SendGrid::EventWebhookHeader::SIGNATURE] = Fixtures::EventWebhook::SIGNATURE
99
+ options[SendGrid::EventWebhookHeader::TIMESTAMP] = Fixtures::EventWebhook::TIMESTAMP
100
+ request = Rack::MockRequest.env_for('/events', options)
101
+ status, headers, body = middleware.call(request)
102
+ expect(status).to eq(200)
103
+ end
104
+
105
+ it 'should short circuit a request to 403 if there is no signature or timestamp' do
106
+ options = {
107
+ :input => Fixtures::EventWebhook::PAYLOAD,
108
+ 'Content-Type' => "application/json"
109
+ }
110
+ request = Rack::MockRequest.env_for('/events', options)
111
+ status, headers, body = middleware.call(request)
112
+ expect(status).to eq(403)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ require './spec/fixtures/event_webhook'
3
+
4
+ describe SendGrid::EventWebhook do
5
+ describe '.verify_signature' do
6
+ it 'verifies a valid signature' do
7
+ unless skip_jruby
8
+ expect(verify(
9
+ Fixtures::EventWebhook::PUBLIC_KEY,
10
+ Fixtures::EventWebhook::PAYLOAD,
11
+ Fixtures::EventWebhook::SIGNATURE,
12
+ Fixtures::EventWebhook::TIMESTAMP
13
+ )).to be true
14
+ end
15
+ end
16
+
17
+ it 'rejects a bad key' do
18
+ unless skip_jruby
19
+ expect(verify(
20
+ Fixtures::EventWebhook::FAILING_PUBLIC_KEY,
21
+ Fixtures::EventWebhook::PAYLOAD,
22
+ Fixtures::EventWebhook::SIGNATURE,
23
+ Fixtures::EventWebhook::TIMESTAMP
24
+ )).to be false
25
+ end
26
+ end
27
+
28
+ it 'rejects a bad payload' do
29
+ unless skip_jruby
30
+ expect(verify(
31
+ Fixtures::EventWebhook::PUBLIC_KEY,
32
+ 'payload',
33
+ Fixtures::EventWebhook::SIGNATURE,
34
+ Fixtures::EventWebhook::TIMESTAMP
35
+ )).to be false
36
+ end
37
+ end
38
+
39
+ it 'rejects a bad signature' do
40
+ unless skip_jruby
41
+ expect(verify(
42
+ Fixtures::EventWebhook::PUBLIC_KEY,
43
+ Fixtures::EventWebhook::PAYLOAD,
44
+ Fixtures::EventWebhook::FAILING_SIGNATURE,
45
+ Fixtures::EventWebhook::TIMESTAMP
46
+ )).to be false
47
+ end
48
+ end
49
+
50
+ it 'rejects a bad timestamp' do
51
+ unless skip_jruby
52
+ expect(verify(
53
+ Fixtures::EventWebhook::PUBLIC_KEY,
54
+ Fixtures::EventWebhook::PAYLOAD,
55
+ Fixtures::EventWebhook::SIGNATURE,
56
+ 'timestamp'
57
+ )).to be false
58
+ end
59
+ end
60
+
61
+ it 'rejects a missing signature' do
62
+ unless skip_jruby
63
+ expect(verify(
64
+ Fixtures::EventWebhook::PUBLIC_KEY,
65
+ Fixtures::EventWebhook::PAYLOAD,
66
+ nil,
67
+ Fixtures::EventWebhook::TIMESTAMP
68
+ )).to be false
69
+ end
70
+ end
71
+
72
+ it 'throws an error when using jruby' do
73
+ if skip_jruby
74
+ expect{ verify(
75
+ Fixtures::EventWebhook::PUBLIC_KEY,
76
+ Fixtures::EventWebhook::PAYLOAD,
77
+ Fixtures::EventWebhook::SIGNATURE,
78
+ Fixtures::EventWebhook::TIMESTAMP
79
+ )}.to raise_error(SendGrid::EventWebhook::NotSupportedError)
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ describe SendGrid::EventWebhookHeader do
86
+ it 'sets the signature header constant' do
87
+ expect(SendGrid::EventWebhookHeader::SIGNATURE).to eq("HTTP_X_TWILIO_EMAIL_EVENT_WEBHOOK_SIGNATURE")
88
+ end
89
+
90
+ it 'sets the timestamp header constant' do
91
+ expect(SendGrid::EventWebhookHeader::TIMESTAMP).to eq("HTTP_X_TWILIO_EMAIL_EVENT_WEBHOOK_TIMESTAMP")
92
+ end
93
+ end
94
+
95
+ def verify(public_key, payload, signature, timestamp)
96
+ ew = SendGrid::EventWebhook.new
97
+ ec_public_key = ew.convert_public_key_to_ecdsa(public_key)
98
+ ew.verify_signature(ec_public_key, payload, signature, timestamp)
99
+ end
100
+
101
+ def skip_jruby
102
+ RUBY_PLATFORM == 'java'
103
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe SendGrid::API do
4
+ describe '.new' do
5
+ it 'initializes correctly' do
6
+ mail_client = SendGrid::API.new(api_key: 'fake_key')
7
+ expect(mail_client.request_headers['Authorization']).to eq('Bearer fake_key')
8
+ expect(mail_client.host).to eq('https://api.sendgrid.com')
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe TwilioEmail::API do
4
+ describe '.new' do
5
+ it 'initializes correctly' do
6
+ mail_client = TwilioEmail::API.new(username: 'username', password: 'password')
7
+ expect(mail_client.request_headers['Authorization']).to eq('Basic dXNlcm5hbWU6cGFzc3dvcmQ=')
8
+ expect(mail_client.host).to eq('https://email.twilio.com')
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,5 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
1
3
  require 'rubygems'
2
4
  require 'bundler/setup'
3
5
  require 'pry'
@@ -1,5 +1,5 @@
1
1
  require_relative "../../../../lib/sendgrid/helpers/mail/mail"
2
- require_relative "../../../../lib/sendgrid/client"
2
+ require_relative "../../../../lib/sendgrid/sendgrid"
3
3
  include SendGrid
4
4
  require "json"
5
5
  require 'minitest/autorun'
@@ -1,3 +1,5 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
1
3
  require_relative '../../lib/sendgrid-ruby.rb'
2
4
  require 'ruby_http_client'
3
5
  require 'minitest/autorun'
@@ -5,34 +7,8 @@ require 'minitest/unit'
5
7
 
6
8
  class TestAPI < MiniTest::Test
7
9
 
8
- unless File.exist?('/usr/local/bin/prism') || File.exist?(File.join(Dir.pwd, 'prism/bin/prism'))
9
- if RUBY_PLATFORM =~ /mswin|mingw/
10
- puts 'Please download the Windows binary (https://github.com/stoplightio/prism/releases) and place it in your /usr/local/bin directory'
11
- else
12
- puts 'Installing Prism'
13
- IO.popen(['curl', '-s', 'https://raw.githubusercontent.com/stoplightio/prism/master/install.sh']) do |io|
14
- out = io.read
15
- unless system(out)
16
- puts "Error downloading the prism binary, you can try downloading directly here (https://github.com/stoplightio/prism/releases) and place in your /usr/local/bin directory, #{out}"
17
- exit
18
- end
19
- end
20
- end
21
- end
22
-
23
- puts 'Activating Prism (~20 seconds)'
24
- @@prism_pid = spawn('prism run --mock --list --spec https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json', [:out, :err] => '/dev/null')
25
- sleep(15)
26
- puts 'Prism started'
27
-
28
10
  def setup
29
- host = "http://localhost:4010"
30
- @sg = SendGrid::API.new(api_key: "SENDGRID_API_KEY", host: host)
31
- end
32
-
33
- Minitest.after_run do
34
- Process.kill('TERM', @@prism_pid)
35
- puts 'Prism shut down'
11
+ @sg = SendGrid::API.new(api_key: "SENDGRID_API_KEY")
36
12
  end
37
13
 
38
14
  def test_init
@@ -58,7 +34,7 @@ class TestAPI < MiniTest::Test
58
34
  assert_equal(test_headers, sg.request_headers)
59
35
  assert_equal("v3", sg.version)
60
36
  assert_equal(subuser, sg.impersonate_subuser)
61
- assert_equal("6.1.4", SendGrid::VERSION)
37
+ assert_equal("6.3.3", SendGrid::VERSION)
62
38
  assert_instance_of(SendGrid::Client, sg.client)
63
39
  end
64
40
 
@@ -2676,76 +2652,65 @@ class TestAPI < MiniTest::Test
2676
2652
  self.assert_equal('200', response.status_code)
2677
2653
  end
2678
2654
 
2679
-
2680
- def test_license_file_correct_year_range
2681
- if File.exist?('./LICENSE.md')
2682
- # get only the first line from the license txt file
2683
- year_range = File.open('./LICENSE.md', &:readline).gsub(/[^\d-]/, '')
2684
- self.assert_equal("#{Time.now.year}", year_range)
2685
- end
2655
+ def test_license_file_year
2656
+ # Read the third line from the license file
2657
+ year = IO.readlines('./LICENSE.md')[2].gsub(/[^\d]/, '')
2658
+ self.assert_equal("#{Time.now.year}", year)
2686
2659
  end
2687
2660
 
2688
- def test_docker_exists
2689
- assert(File.file?('./Docker') || File.file?('./docker/Dockerfile'))
2690
- end
2691
-
2692
- # def test_docker_compose_exists
2693
- # assert(File.file?('./docker-compose.yml') || File.file?('./docker/docker-compose.yml'))
2694
- # end
2695
-
2696
2661
  def test_env_sample_exists
2697
- assert(File.file?('./.env_sample'))
2662
+ assert(File.file?('./.env_sample'))
2698
2663
  end
2699
2664
 
2700
2665
  def test_gitignore_exists
2701
- assert(File.file?('./.gitignore'))
2666
+ assert(File.file?('./.gitignore'))
2702
2667
  end
2703
2668
 
2704
2669
  def test_travis_exists
2705
- assert(File.file?('./.travis.yml'))
2670
+ assert(File.file?('./.travis.yml'))
2706
2671
  end
2707
2672
 
2708
2673
  def test_codeclimate_exists
2709
- assert(File.file?('./.codeclimate.yml'))
2674
+ assert(File.file?('./.codeclimate.yml'))
2710
2675
  end
2711
2676
 
2712
2677
  def test_changelog_exists
2713
- assert(File.file?('./CHANGELOG.md'))
2678
+ assert(File.file?('./CHANGELOG.md'))
2714
2679
  end
2715
2680
 
2716
2681
  def test_code_of_conduct_exists
2717
- assert(File.file?('./CODE_OF_CONDUCT.md'))
2682
+ assert(File.file?('./CODE_OF_CONDUCT.md'))
2718
2683
  end
2719
2684
 
2720
2685
  def test_contributing_exists
2721
- assert(File.file?('./CONTRIBUTING.md'))
2686
+ assert(File.file?('./CONTRIBUTING.md'))
2722
2687
  end
2723
2688
 
2724
2689
  def test_issue_template_exists
2725
- assert(File.file?('./ISSUE_TEMPLATE.md'))
2690
+ assert(File.file?('./ISSUE_TEMPLATE.md'))
2726
2691
  end
2727
2692
 
2728
2693
  def test_license_exists
2729
- assert(File.file?('./LICENSE.md'))
2694
+ assert(File.file?('./LICENSE.md'))
2730
2695
  end
2731
2696
 
2732
- def test_pull_request_template_exists
2733
- assert(File.file?('./PULL_REQUEST_TEMPLATE.md'))
2697
+ def test_pr_template_exists
2698
+ assert(File.file?('./PULL_REQUEST_TEMPLATE.md'))
2734
2699
  end
2735
2700
 
2736
2701
  def test_readme_exists
2737
- assert(File.file?('./README.md'))
2702
+ assert(File.file?('./README.md'))
2738
2703
  end
2739
2704
 
2740
2705
  def test_troubleshooting_exists
2741
- assert(File.file?('./TROUBLESHOOTING.md'))
2706
+ assert(File.file?('./TROUBLESHOOTING.md'))
2742
2707
  end
2743
2708
 
2744
2709
  def test_usage_exists
2745
- assert(File.file?('./USAGE.md'))
2710
+ assert(File.file?('./USAGE.md'))
2746
2711
  end
2747
2712
 
2748
- def test_use_cases_exists
2749
- assert(File.file?('./USE_CASES.md'))
2713
+ def test_use_cases_readme_exists
2714
+ assert(File.file?('./use-cases/README.md'))
2750
2715
  end
2751
2716
  end
@@ -0,0 +1,16 @@
1
+ This directory provides examples for specific use cases.
2
+
3
+ Please [open an issue](https://github.com/sendgrid/sendgrid-ruby/issues) or [make a pull request](https://github.com/sendgrid/sendgrid-ruby/pulls) for any use cases you would like us to document here. Thank you!
4
+
5
+ # Email Use Cases
6
+ * [Transactional Templates](transactional-templates.md)
7
+ * [Legacy Templates](legacy-templates.md)
8
+
9
+ # Twilio Use Cases
10
+ * [Twilio Setup](twilio-setup.md)
11
+ * [Send an Email With Twilio Email (Pilot)](twilio-email.md)
12
+ * [Send an SMS Message](sms.md)
13
+
14
+ # Non-Email Use Cases
15
+ * [How to Set up a Domain Authentication](domain-authentication.md)
16
+ * [How to View Email Statistics](email-statistics.md)
@@ -0,0 +1,5 @@
1
+ # How to Setup a Domain Authentication
2
+
3
+ You can find documentation for how to setup a domain authentication via the UI [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) and via API [here](https://github.com/sendgrid/sendgrid-ruby/blob/master/USAGE.md#sender-authentication).
4
+
5
+ Find more information about all of Twilio SendGrid's authentication related documentation [here](https://sendgrid.com/docs/ui/account-and-settings/).