mailgun-ruby 1.0.3 → 1.1.0
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/.gitignore +2 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +22 -0
- data/.ruby-env.yml.example +12 -0
- data/.travis.yml +6 -12
- data/Domains.md +36 -0
- data/MessageBuilder.md +14 -14
- data/Messages.md +44 -30
- data/OptInHandler.md +34 -34
- data/README.md +74 -24
- data/Rakefile +22 -20
- data/Snippets.md +26 -26
- data/Webhooks.md +40 -0
- data/lib/mailgun.rb +26 -228
- data/lib/mailgun/chains.rb +16 -0
- data/lib/mailgun/client.rb +143 -0
- data/lib/mailgun/domains/domains.rb +84 -0
- data/lib/mailgun/events/events.rb +53 -35
- data/lib/mailgun/exceptions/exceptions.rb +43 -10
- data/lib/mailgun/lists/opt_in_handler.rb +18 -19
- data/lib/mailgun/messages/batch_message.rb +31 -48
- data/lib/mailgun/messages/message_builder.rb +160 -144
- data/lib/mailgun/response.rb +55 -0
- data/lib/mailgun/version.rb +2 -3
- data/lib/mailgun/webhooks/webhooks.rb +101 -0
- data/mailgun.gemspec +16 -10
- data/spec/integration/bounces_spec.rb +44 -0
- data/spec/integration/campaign_spec.rb +60 -0
- data/spec/integration/complaints_spec.rb +38 -0
- data/spec/integration/domains_spec.rb +39 -0
- data/spec/integration/email_validation_spec.rb +29 -0
- data/spec/integration/events_spec.rb +20 -0
- data/spec/integration/list_members_spec.rb +63 -0
- data/spec/integration/list_spec.rb +58 -0
- data/spec/integration/mailgun_spec.rb +26 -550
- data/spec/integration/routes_spec.rb +74 -0
- data/spec/integration/stats_spec.rb +15 -0
- data/spec/integration/unsubscribes_spec.rb +42 -0
- data/spec/integration/webhook_spec.rb +54 -0
- data/spec/spec_helper.rb +37 -7
- data/spec/unit/connection/test_client.rb +15 -95
- data/spec/unit/events/events_spec.rb +9 -6
- data/spec/unit/lists/opt_in_handler_spec.rb +6 -4
- data/spec/unit/mailgun_spec.rb +25 -19
- data/spec/unit/messages/batch_message_spec.rb +47 -38
- data/spec/unit/messages/message_builder_spec.rb +282 -111
- data/vcr_cassettes/bounces.yml +175 -0
- data/vcr_cassettes/complaints.yml +175 -0
- data/vcr_cassettes/domains.todo.yml +42 -0
- data/vcr_cassettes/domains.yml +360 -0
- data/vcr_cassettes/email_validation.yml +104 -0
- data/vcr_cassettes/events.yml +61 -0
- data/vcr_cassettes/list_members.yml +320 -0
- data/vcr_cassettes/mailing_list.todo.yml +43 -0
- data/vcr_cassettes/mailing_list.yml +390 -0
- data/vcr_cassettes/routes.yml +359 -0
- data/vcr_cassettes/send_message.yml +107 -0
- data/vcr_cassettes/stats.yml +44 -0
- data/vcr_cassettes/unsubscribes.yml +191 -0
- data/vcr_cassettes/webhooks.yml +276 -0
- metadata +114 -10
@@ -0,0 +1,16 @@
|
|
1
|
+
module Mailgun
|
2
|
+
|
3
|
+
# Public constants used throughout
|
4
|
+
class Chains
|
5
|
+
|
6
|
+
# maximum campaign ids per message
|
7
|
+
MAX_CAMPAIGN_IDS = 3
|
8
|
+
|
9
|
+
# maximum tags per message
|
10
|
+
MAX_TAGS = 3
|
11
|
+
|
12
|
+
# maximum recipients per message or batch
|
13
|
+
MAX_RECIPIENTS = 1000
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'mailgun/chains'
|
2
|
+
require 'mailgun/exceptions/exceptions'
|
3
|
+
|
4
|
+
module Mailgun
|
5
|
+
# A Mailgun::Client object is used to communicate with the Mailgun API. It is a
|
6
|
+
# wrapper around RestClient so you don't have to worry about the HTTP aspect
|
7
|
+
# of communicating with our API.
|
8
|
+
#
|
9
|
+
# See the Github documentation for full examples.
|
10
|
+
class Client
|
11
|
+
|
12
|
+
def initialize(api_key = Mailgun.api_key,
|
13
|
+
api_host = 'api.mailgun.net',
|
14
|
+
api_version = 'v3',
|
15
|
+
ssl = true)
|
16
|
+
|
17
|
+
endpoint = endpoint_generator(api_host, api_version, ssl)
|
18
|
+
@http_client = RestClient::Resource.new(endpoint,
|
19
|
+
user: 'api',
|
20
|
+
password: api_key,
|
21
|
+
user_agent: "mailgun-sdk-ruby/#{Mailgun::VERSION}")
|
22
|
+
end
|
23
|
+
|
24
|
+
# Simple Message Sending
|
25
|
+
#
|
26
|
+
# @param [String] working_domain This is the domain you wish to send from.
|
27
|
+
# @param [Hash] data This should be a standard Hash
|
28
|
+
# containing required parameters for the requested resource.
|
29
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
30
|
+
def send_message(working_domain, data)
|
31
|
+
case data
|
32
|
+
when Hash
|
33
|
+
if data.key?(:message)
|
34
|
+
if data[:message].is_a?(String)
|
35
|
+
data[:message] = convert_string_to_file(data[:message])
|
36
|
+
end
|
37
|
+
return post("#{working_domain}/messages.mime", data)
|
38
|
+
end
|
39
|
+
post("#{working_domain}/messages", data)
|
40
|
+
when MessageBuilder
|
41
|
+
post("#{working_domain}/messages", data.message)
|
42
|
+
else
|
43
|
+
fail ParameterError.new('Unknown data type for data parameter.', data)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Generic Mailgun POST Handler
|
48
|
+
#
|
49
|
+
# @param [String] resource_path This is the API resource you wish to interact
|
50
|
+
# with. Be sure to include your domain, where necessary.
|
51
|
+
# @param [Hash] data This should be a standard Hash
|
52
|
+
# containing required parameters for the requested resource.
|
53
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
54
|
+
def post(resource_path, data)
|
55
|
+
response = @http_client[resource_path].post(data)
|
56
|
+
Response.new(response)
|
57
|
+
rescue => err
|
58
|
+
raise communication_error err
|
59
|
+
end
|
60
|
+
|
61
|
+
# Generic Mailgun GET Handler
|
62
|
+
#
|
63
|
+
# @param [String] resource_path This is the API resource you wish to interact
|
64
|
+
# with. Be sure to include your domain, where necessary.
|
65
|
+
# @param [Hash] query_string This should be a standard Hash
|
66
|
+
# containing required parameters for the requested resource.
|
67
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
68
|
+
def get(resource_path, params = nil, accept = '*/*')
|
69
|
+
if params
|
70
|
+
response = @http_client[resource_path].get(params: params, accept: accept)
|
71
|
+
else
|
72
|
+
response = @http_client[resource_path].get(accept: accept)
|
73
|
+
end
|
74
|
+
Response.new(response)
|
75
|
+
rescue => err
|
76
|
+
raise communication_error err
|
77
|
+
end
|
78
|
+
|
79
|
+
# Generic Mailgun PUT Handler
|
80
|
+
#
|
81
|
+
# @param [String] resource_path This is the API resource you wish to interact
|
82
|
+
# with. Be sure to include your domain, where necessary.
|
83
|
+
# @param [Hash] data This should be a standard Hash
|
84
|
+
# containing required parameters for the requested resource.
|
85
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
86
|
+
def put(resource_path, data)
|
87
|
+
response = @http_client[resource_path].put(data)
|
88
|
+
Response.new(response)
|
89
|
+
rescue => err
|
90
|
+
raise communication_error err
|
91
|
+
end
|
92
|
+
|
93
|
+
# Generic Mailgun DELETE Handler
|
94
|
+
#
|
95
|
+
# @param [String] resource_path This is the API resource you wish to interact
|
96
|
+
# with. Be sure to include your domain, where necessary.
|
97
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
98
|
+
def delete(resource_path)
|
99
|
+
response = @http_client[resource_path].delete
|
100
|
+
Response.new(response)
|
101
|
+
rescue => err
|
102
|
+
raise communication_error err
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# Converts MIME string to file for easy uploading to API
|
108
|
+
#
|
109
|
+
# @param [String] string MIME string to post to API
|
110
|
+
# @return [File] File object
|
111
|
+
def convert_string_to_file(string)
|
112
|
+
file = Tempfile.new('MG_TMP_MIME')
|
113
|
+
file.write(string)
|
114
|
+
file.rewind
|
115
|
+
file
|
116
|
+
end
|
117
|
+
|
118
|
+
# Generates the endpoint URL to for the API. Allows overriding
|
119
|
+
# API endpoint, API versions, and toggling SSL.
|
120
|
+
#
|
121
|
+
# @param [String] api_host URL endpoint the library will hit
|
122
|
+
# @param [String] api_version The version of the API to hit
|
123
|
+
# @param [Boolean] ssl True, SSL. False, No SSL.
|
124
|
+
# @return [string] concatenated URL string
|
125
|
+
def endpoint_generator(api_host, api_version, ssl)
|
126
|
+
ssl ? scheme = 'https' : scheme = 'http'
|
127
|
+
if api_version
|
128
|
+
"#{scheme}://#{api_host}/#{api_version}"
|
129
|
+
else
|
130
|
+
"#{scheme}://#{api_host}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Raises CommunicationError and stores response in it if present
|
135
|
+
#
|
136
|
+
# @param [StandardException] e upstream exception object
|
137
|
+
def communication_error(e)
|
138
|
+
return CommunicationError.new(e.message, e.response) if e.respond_to? :response
|
139
|
+
CommunicationError.new(e.message)
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'mailgun/exceptions/exceptions'
|
2
|
+
|
3
|
+
module Mailgun
|
4
|
+
|
5
|
+
# A Mailgun::Domains object is a simple CRUD interface to Mailgun Domains.
|
6
|
+
# Uses Mailgun
|
7
|
+
class Domains
|
8
|
+
|
9
|
+
# Public: creates a new Mailgun::Domains instance.
|
10
|
+
# Defaults to Mailgun::Client
|
11
|
+
def initialize(client = Mailgun::Client.new)
|
12
|
+
@client = client
|
13
|
+
end
|
14
|
+
|
15
|
+
# Public: Get Domains
|
16
|
+
#
|
17
|
+
# limit - [Integer] Maximum number of records to return. (100 by default)
|
18
|
+
# skip - [Integer] Number of records to skip. (0 by default)
|
19
|
+
#
|
20
|
+
# Returns [Array] A list of domains (hash)
|
21
|
+
def list(options = {})
|
22
|
+
@client.get('domains', options).to_h['items']
|
23
|
+
end
|
24
|
+
alias_method :get_domains, :list
|
25
|
+
|
26
|
+
# Public: Get domain information
|
27
|
+
#
|
28
|
+
# domain - [String] Domain name to lookup
|
29
|
+
#
|
30
|
+
# Returns [Hash] Information on the requested domains.
|
31
|
+
def info(domain)
|
32
|
+
fail(ParameterError, 'No domain given to find on Mailgun', caller) unless domain
|
33
|
+
@client.get("domains/#{domain}").to_h!
|
34
|
+
end
|
35
|
+
alias_method :get, :info
|
36
|
+
alias_method :get_domain, :info
|
37
|
+
|
38
|
+
# Public: Verify domain, update domain records
|
39
|
+
# Unknown status - this is not in the current Mailgun API
|
40
|
+
# Do no rely on this being available in future releases.
|
41
|
+
#
|
42
|
+
# domain - [String] Domain name
|
43
|
+
#
|
44
|
+
# Returns [Hash] Information on the updated/verified domains
|
45
|
+
def verify(domain)
|
46
|
+
fail(ParameterError, 'No domain given to verify on Mailgun', caller) unless domain
|
47
|
+
@client.put("domains/#{domain}/verify", nil).to_h!
|
48
|
+
end
|
49
|
+
alias_method :verify_domain, :verify
|
50
|
+
|
51
|
+
# Public: Add domain
|
52
|
+
#
|
53
|
+
# domain - [String] Name of the domain (ex. domain.com)
|
54
|
+
# options - [Hash] of
|
55
|
+
# smtp_password - [String] Password for SMTP authentication
|
56
|
+
# spam_action - [String] disabled or tag
|
57
|
+
# Disable, no spam filtering will occur for inbound messages.
|
58
|
+
# Tag, messages will be tagged wtih a spam header. See Spam Filter.
|
59
|
+
# wildcard - [Boolean] true or false Determines whether the domain will accept email for sub-domains.
|
60
|
+
#
|
61
|
+
# Returns [Hash] of created domain
|
62
|
+
def create(domain, options = {})
|
63
|
+
fail(ParameterError, 'No domain given to add on Mailgun', caller) unless domain
|
64
|
+
options = { smtp_password: nil, spam_action: 'disabled', wildcard: false }.merge(options)
|
65
|
+
options[:name] = domain
|
66
|
+
@client.post('domains', options).to_h
|
67
|
+
end
|
68
|
+
alias_method :add, :create
|
69
|
+
alias_method :add_domain, :create
|
70
|
+
|
71
|
+
# Public: Delete Domain
|
72
|
+
#
|
73
|
+
# domain - [String] domain name to delete (ex. domain.com)
|
74
|
+
#
|
75
|
+
# Returns [Boolean] if successful or not
|
76
|
+
def remove(domain)
|
77
|
+
fail(ParameterError, 'No domain given to remove on Mailgun', caller) unless domain
|
78
|
+
@client.delete("domains/#{domain}").to_h['message'] == 'Domain has been deleted'
|
79
|
+
end
|
80
|
+
alias_method :delete, :remove
|
81
|
+
alias_method :delete_domain, :remove
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -1,17 +1,21 @@
|
|
1
|
-
require 'mailgun'
|
2
|
-
require "mailgun/exceptions/exceptions"
|
3
|
-
|
1
|
+
require 'mailgun/exceptions/exceptions'
|
4
2
|
|
5
3
|
module Mailgun
|
6
4
|
|
7
5
|
# A Mailgun::Events object makes it really simple to consume
|
8
|
-
#
|
6
|
+
# Mailgun's events from the Events endpoint.
|
9
7
|
#
|
8
|
+
# This is not yet comprehensive.
|
10
9
|
#
|
11
|
-
#
|
12
|
-
|
10
|
+
# Examples
|
11
|
+
#
|
12
|
+
# See the Github documentation for full examples.
|
13
13
|
class Events
|
14
14
|
|
15
|
+
# Public: event initializer
|
16
|
+
#
|
17
|
+
# client - an instance of Mailgun::Client
|
18
|
+
# domain - the domain to build queries
|
15
19
|
def initialize(client, domain)
|
16
20
|
@client = client
|
17
21
|
@domain = domain
|
@@ -19,56 +23,70 @@ module Mailgun
|
|
19
23
|
@paging_previous = nil
|
20
24
|
end
|
21
25
|
|
22
|
-
# Issues a simple get against the client.
|
26
|
+
# Public: Issues a simple get against the client.
|
23
27
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
def get(params=nil)
|
28
|
-
|
28
|
+
# params - a Hash of query options and/or filters.
|
29
|
+
#
|
30
|
+
# Returns a Mailgun::Response object.
|
31
|
+
def get(params = nil)
|
32
|
+
get_events(params)
|
29
33
|
end
|
30
34
|
|
31
|
-
# Using built in paging, obtains the next set of data.
|
35
|
+
# Public: Using built in paging, obtains the next set of data.
|
36
|
+
# If an events request hasn't been sent previously, this will send one
|
37
|
+
# without parameters
|
32
38
|
#
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
_get(nil, @paging_next)
|
39
|
+
# Returns a Mailgun::Response object.
|
40
|
+
def next
|
41
|
+
get_events(nil, @paging_next)
|
37
42
|
end
|
38
43
|
|
39
|
-
# Using built in paging, obtains the previous set of data.
|
44
|
+
# Public: Using built in paging, obtains the previous set of data.
|
45
|
+
# If an events request hasn't been sent previously, this will send one
|
46
|
+
# without parameters
|
40
47
|
#
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
_get(nil, @paging_previous)
|
48
|
+
# Returns Mailgun::Response object.
|
49
|
+
def previous
|
50
|
+
get_events(nil, @paging_previous)
|
45
51
|
end
|
46
52
|
|
47
53
|
private
|
48
54
|
|
49
|
-
|
55
|
+
# Internal: Makes and processes the event request through the client
|
56
|
+
#
|
57
|
+
# params - optional Hash of query options
|
58
|
+
# paging - the URL key used for previous/next requests
|
59
|
+
#
|
60
|
+
# Returns a Mailgun.Response object.
|
61
|
+
def get_events(params = nil, paging = nil)
|
50
62
|
response = @client.get(construct_url(paging), params)
|
51
63
|
extract_paging(response)
|
52
64
|
response
|
53
65
|
end
|
54
66
|
|
67
|
+
# Internal: given an event response, pull and store the paging keys
|
68
|
+
#
|
69
|
+
# response - a Mailgun::Response object
|
70
|
+
#
|
71
|
+
# Return is irrelevant.
|
55
72
|
def extract_paging(response)
|
56
|
-
paging_next = response.to_h["paging"]["next"]
|
57
|
-
paging_previous = response.to_h["paging"]["previous"]
|
58
|
-
|
59
73
|
# This is pretty hackish. But the URL will never change in API v2.
|
60
|
-
@paging_next =
|
61
|
-
@paging_previous =
|
74
|
+
@paging_next = response.to_h['paging']['next'].split('/')[6]
|
75
|
+
@paging_previous = response.to_h['paging']['previous'].split('/')[6]
|
76
|
+
rescue
|
77
|
+
@paging_next = nil
|
78
|
+
@paging_previous = nil
|
62
79
|
end
|
63
80
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
81
|
+
# Internal: construct the event path to be used by the client
|
82
|
+
#
|
83
|
+
# paging - the URL key for previous/next set of results
|
84
|
+
#
|
85
|
+
# Returns a String of the partial URI
|
86
|
+
def construct_url(paging = nil)
|
87
|
+
return "#{@domain}/events/#{paging}" if paging
|
88
|
+
"#{@domain}/events"
|
70
89
|
end
|
71
90
|
|
72
91
|
end
|
73
|
-
|
74
92
|
end
|
@@ -1,20 +1,53 @@
|
|
1
1
|
module Mailgun
|
2
2
|
|
3
|
-
class
|
3
|
+
# Public: A basic class for mananging errors.
|
4
|
+
# Inherits from StandardError (previously RuntimeError) as not all errors are
|
5
|
+
# runtime errors.
|
6
|
+
class Error < StandardError
|
7
|
+
|
8
|
+
# Public: get an object an error is instantiated with
|
4
9
|
attr_reader :object
|
5
10
|
|
6
|
-
|
7
|
-
|
11
|
+
# Public: initialize a Mailgun:Error object
|
12
|
+
#
|
13
|
+
# message - a String describing the error
|
14
|
+
# object - an object with details about the error
|
15
|
+
def initialize(message = nil, object = nil)
|
16
|
+
super(message)
|
8
17
|
@object = object
|
9
18
|
end
|
10
|
-
|
11
|
-
def to_s
|
12
|
-
@message || self.class.to_s
|
13
|
-
end
|
14
19
|
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
class
|
21
|
+
# Public: Class for managing parameter errors, with a pretty name.
|
22
|
+
# Inherits from Mailgun::Error
|
23
|
+
class ParameterError < Error; end
|
24
|
+
|
25
|
+
# Public: Class for managing parsing errors, with a pretty name.
|
26
|
+
# Inherits from Mailgun::Error
|
27
|
+
class ParseError < Error; end
|
28
|
+
|
29
|
+
# Public: Class for managing communications (eg http) response errors
|
30
|
+
# Inherits from Mailgun::Error
|
31
|
+
class CommunicationError < Error
|
32
|
+
# Public: gets HTTP status code
|
33
|
+
attr_reader :code
|
19
34
|
|
35
|
+
# Public: fallback if there is no response code on the object
|
36
|
+
NOCODE = 000
|
37
|
+
|
38
|
+
# Public: initialization of new error given a message and/or object
|
39
|
+
#
|
40
|
+
# message - a String detailing the error
|
41
|
+
# object - a RestClient::Reponse object
|
42
|
+
#
|
43
|
+
def initialize(message = nil, object = nil)
|
44
|
+
@object = object
|
45
|
+
@code = object.http_code || NOCODE
|
46
|
+
super(JSON.parse(object.body)['message'])
|
47
|
+
rescue NoMethodError, JSON::ParserError
|
48
|
+
@code = NOCODE
|
49
|
+
super(message)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
20
53
|
end
|
@@ -5,6 +5,10 @@ require 'openssl'
|
|
5
5
|
|
6
6
|
module Mailgun
|
7
7
|
|
8
|
+
# Public: Provides methods for creating and handling opt-in URLs,
|
9
|
+
# particularlly for mailing lists.
|
10
|
+
#
|
11
|
+
# See: https://github.com/mailgun/mailgun-ruby/blob/master/OptInHandler.md
|
8
12
|
class OptInHandler
|
9
13
|
|
10
14
|
# Generates a hash that can be used to validate opt-in recipients. Encodes
|
@@ -14,22 +18,19 @@ module Mailgun
|
|
14
18
|
# @param [String] secret_app_id A secret passphrase used as a constant for the hash.
|
15
19
|
# @param [Hash] recipient_address The address of the user that should be subscribed.
|
16
20
|
# @return [String] A url encoded URL suffix hash.
|
17
|
-
|
18
21
|
def self.generate_hash(mailing_list, secret_app_id, recipient_address)
|
19
|
-
|
20
|
-
'r' => recipient_address}
|
22
|
+
inner_payload = { 'l' => mailing_list, 'r' => recipient_address }
|
21
23
|
|
22
|
-
|
24
|
+
inner_payload_encoded = Base64.encode64(JSON.generate(inner_payload))
|
23
25
|
|
24
26
|
sha1_digest = OpenSSL::Digest.new('sha1')
|
25
|
-
digest = OpenSSL::HMAC.hexdigest(sha1_digest, secret_app_id,
|
27
|
+
digest = OpenSSL::HMAC.hexdigest(sha1_digest, secret_app_id, inner_payload_encoded)
|
26
28
|
|
27
|
-
|
28
|
-
'p' => innerPayloadEncoded}
|
29
|
+
outer_payload = { 'h' => digest, 'p' => inner_payload_encoded }
|
29
30
|
|
30
|
-
|
31
|
+
outer_payload_encoded = Base64.encode64(JSON.generate(outer_payload))
|
31
32
|
|
32
|
-
|
33
|
+
CGI.escape(outer_payload_encoded)
|
33
34
|
end
|
34
35
|
|
35
36
|
# Validates the hash provided from the generate_hash method.
|
@@ -37,24 +38,22 @@ module Mailgun
|
|
37
38
|
# @param [String] secret_app_id A secret passphrase used as a constant for the hash.
|
38
39
|
# @param [Hash] unique_hash The hash from the user. Likely via link click.
|
39
40
|
# @return [Hash or Boolean] A hash with 'recipient_address' and 'mailing_list', if validates. Otherwise, boolean false.
|
40
|
-
|
41
41
|
def self.validate_hash(secret_app_id, unique_hash)
|
42
|
-
|
42
|
+
outer_payload = JSON.parse(Base64.decode64(CGI.unescape(unique_hash)))
|
43
43
|
|
44
44
|
sha1_digest = OpenSSL::Digest.new('sha1')
|
45
|
-
generated_hash = OpenSSL::HMAC.hexdigest(sha1_digest, secret_app_id,
|
45
|
+
generated_hash = OpenSSL::HMAC.hexdigest(sha1_digest, secret_app_id, outer_payload['p'])
|
46
46
|
|
47
|
-
|
47
|
+
inner_payload = JSON.parse(Base64.decode64(CGI.unescape(outer_payload['p'])))
|
48
48
|
|
49
|
-
hash_provided =
|
49
|
+
hash_provided = outer_payload['h']
|
50
50
|
|
51
|
-
if
|
52
|
-
return {'recipient_address' =>
|
53
|
-
else
|
54
|
-
return false
|
51
|
+
if generated_hash == hash_provided
|
52
|
+
return { 'recipient_address' => inner_payload['r'], 'mailing_list' => inner_payload['l'] }
|
55
53
|
end
|
54
|
+
false
|
56
55
|
end
|
57
56
|
|
58
57
|
end
|
59
|
-
|
58
|
+
|
60
59
|
end
|