mailgun-ruby 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|