aws-ses-v4 0.8.1
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 +7 -0
- data/.document +5 -0
- data/CHANGELOG +59 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +80 -0
- data/LICENSE +20 -0
- data/README.erb +97 -0
- data/README.rdoc +220 -0
- data/Rakefile +83 -0
- data/TODO +3 -0
- data/VERSION +1 -0
- data/aws-ses.gemspec +102 -0
- data/lib/aws/actionmailer/ses_extension.rb +19 -0
- data/lib/aws/ses.rb +28 -0
- data/lib/aws/ses/addresses.rb +75 -0
- data/lib/aws/ses/base.rb +286 -0
- data/lib/aws/ses/extensions.rb +39 -0
- data/lib/aws/ses/info.rb +100 -0
- data/lib/aws/ses/response.rb +110 -0
- data/lib/aws/ses/send_email.rb +159 -0
- data/lib/aws/ses/version.rb +12 -0
- data/test/address_test.rb +72 -0
- data/test/base_test.rb +104 -0
- data/test/extensions_test.rb +111 -0
- data/test/fixtures.rb +89 -0
- data/test/helper.rb +63 -0
- data/test/info_test.rb +108 -0
- data/test/mocks/fake_response.rb +26 -0
- data/test/response_test.rb +26 -0
- data/test/send_email_test.rb +144 -0
- metadata +231 -0
data/Rakefile
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'erb'
|
13
|
+
|
14
|
+
require 'rake/testtask'
|
15
|
+
Rake::TestTask.new(:test) do |test|
|
16
|
+
test.libs << 'lib' << 'test'
|
17
|
+
test.pattern = 'test/**/*_test.rb'
|
18
|
+
test.verbose = true
|
19
|
+
end
|
20
|
+
|
21
|
+
# require 'rcov/rcovtask'
|
22
|
+
# Rcov::RcovTask.new do |test|
|
23
|
+
# test.libs << 'test'
|
24
|
+
# test.pattern = 'test/**/*_test.rb'
|
25
|
+
# test.verbose = true
|
26
|
+
# end
|
27
|
+
|
28
|
+
task :default => :test
|
29
|
+
|
30
|
+
require 'rdoc/task'
|
31
|
+
require File.dirname(__FILE__) + '/lib/aws/ses'
|
32
|
+
|
33
|
+
namespace :doc do
|
34
|
+
Rake::RDocTask.new do |rdoc|
|
35
|
+
rdoc.rdoc_dir = 'doc'
|
36
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
37
|
+
rdoc.title = "AWS::SES -- Support for Amazon SES's REST api #{version}"
|
38
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
39
|
+
rdoc.rdoc_files.include('README.rdoc')
|
40
|
+
rdoc.rdoc_files.include('LICENSE')
|
41
|
+
rdoc.rdoc_files.include('CHANGELOG')
|
42
|
+
rdoc.rdoc_files.include('TODO')
|
43
|
+
rdoc.rdoc_files.include('VERSION')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
46
|
+
|
47
|
+
task :rdoc => 'doc:readme'
|
48
|
+
|
49
|
+
task :refresh => :rerdoc do
|
50
|
+
system 'open doc/index.html'
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Generate readme.rdoc from readme.erb"
|
54
|
+
task :readme do
|
55
|
+
require 'support/rdoc/code_info'
|
56
|
+
RDoc::CodeInfo.parse('lib/**/*.rb')
|
57
|
+
|
58
|
+
strip_comments = lambda {|comment| comment.gsub(/^# ?/, '')}
|
59
|
+
docs_for = lambda do |location|
|
60
|
+
info = RDoc::CodeInfo.for(location)
|
61
|
+
raise RuntimeError, "Couldn't find documentation for `#{location}'" unless info
|
62
|
+
strip_comments[info.comment]
|
63
|
+
end
|
64
|
+
|
65
|
+
open('README.rdoc', 'w') do |file|
|
66
|
+
file.write ERB.new(IO.read('README.erb')).result(binding)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
require 'jeweler'
|
72
|
+
Jeweler::Tasks.new do |gem|
|
73
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
74
|
+
gem.name = "aws-ses"
|
75
|
+
gem.homepage = "http://github.com/drewblas/aws-ses"
|
76
|
+
gem.license = "MIT"
|
77
|
+
gem.summary = "Client library for Amazon's Simple Email Service's REST API"
|
78
|
+
gem.description = "Client library for Amazon's Simple Email Service's REST API"
|
79
|
+
gem.email = "drew.blas@gmail.com"
|
80
|
+
gem.authors = ["Drew Blas", "Marcel Molina Jr."]
|
81
|
+
# dependencies defined in Gemfile
|
82
|
+
end
|
83
|
+
Jeweler::RubygemsDotOrgTasks.new
|
data/TODO
ADDED
@@ -0,0 +1,3 @@
|
|
1
|
+
* Use a better XML parser (and be consistent)
|
2
|
+
* Rename Base to something else (probably Mailer): Nothing else actually inherits from Base, so that is a very poor naming convention. I intend to change it in the future
|
3
|
+
* Integer responses should probably be cast to ints instead of left as strings
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.8.1
|
data/aws-ses.gemspec
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: aws-ses 0.7.1 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "aws-ses-v4".freeze
|
9
|
+
s.version = "0.8.1"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib".freeze]
|
13
|
+
s.authors = ["Drew Blas".freeze, "Marcel Molina Jr.".freeze]
|
14
|
+
s.date = "2021-04-10"
|
15
|
+
s.description = "Client library for Amazon's Simple Email Service's REST API".freeze
|
16
|
+
s.email = "drew.blas@gmail.com".freeze
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"CHANGELOG",
|
19
|
+
"LICENSE",
|
20
|
+
"README.erb",
|
21
|
+
"README.rdoc",
|
22
|
+
"TODO"
|
23
|
+
]
|
24
|
+
s.files = [
|
25
|
+
".document",
|
26
|
+
"CHANGELOG",
|
27
|
+
"Gemfile",
|
28
|
+
"Gemfile.lock",
|
29
|
+
"LICENSE",
|
30
|
+
"README.erb",
|
31
|
+
"README.rdoc",
|
32
|
+
"Rakefile",
|
33
|
+
"TODO",
|
34
|
+
"VERSION",
|
35
|
+
"aws-ses.gemspec",
|
36
|
+
"lib/aws/actionmailer/ses_extension.rb",
|
37
|
+
"lib/aws/ses.rb",
|
38
|
+
"lib/aws/ses/addresses.rb",
|
39
|
+
"lib/aws/ses/base.rb",
|
40
|
+
"lib/aws/ses/extensions.rb",
|
41
|
+
"lib/aws/ses/info.rb",
|
42
|
+
"lib/aws/ses/response.rb",
|
43
|
+
"lib/aws/ses/send_email.rb",
|
44
|
+
"lib/aws/ses/version.rb",
|
45
|
+
"test/address_test.rb",
|
46
|
+
"test/base_test.rb",
|
47
|
+
"test/extensions_test.rb",
|
48
|
+
"test/fixtures.rb",
|
49
|
+
"test/helper.rb",
|
50
|
+
"test/info_test.rb",
|
51
|
+
"test/mocks/fake_response.rb",
|
52
|
+
"test/response_test.rb",
|
53
|
+
"test/send_email_test.rb"
|
54
|
+
]
|
55
|
+
s.homepage = "http://github.com/sertangulveren/aws-ses".freeze
|
56
|
+
s.licenses = ["MIT".freeze]
|
57
|
+
s.rubygems_version = "2.5.2.3".freeze
|
58
|
+
s.summary = "Client library for Amazon's Simple Email Service's REST API".freeze
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
s.specification_version = 4
|
62
|
+
|
63
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
64
|
+
s.add_runtime_dependency(%q<builder>.freeze, [">= 0"])
|
65
|
+
s.add_runtime_dependency(%q<mail>.freeze, ["> 2.2.5"])
|
66
|
+
s.add_runtime_dependency(%q<mime-types>.freeze, [">= 0"])
|
67
|
+
s.add_runtime_dependency(%q<xml-simple>.freeze, [">= 0"])
|
68
|
+
s.add_development_dependency(%q<bundler>.freeze, [">= 1.17"])
|
69
|
+
s.add_development_dependency(%q<flexmock>.freeze, ["~> 0.8.11"])
|
70
|
+
s.add_development_dependency(%q<jeweler>.freeze, [">= 0"])
|
71
|
+
s.add_development_dependency(%q<rake>.freeze, [">= 0"])
|
72
|
+
s.add_development_dependency(%q<shoulda-context>.freeze, [">= 0"])
|
73
|
+
s.add_development_dependency(%q<test-unit>.freeze, [">= 0"])
|
74
|
+
s.add_development_dependency(%q<timecop>.freeze, [">= 0"])
|
75
|
+
else
|
76
|
+
s.add_dependency(%q<builder>.freeze, [">= 0"])
|
77
|
+
s.add_dependency(%q<mail>.freeze, ["> 2.2.5"])
|
78
|
+
s.add_dependency(%q<mime-types>.freeze, [">= 0"])
|
79
|
+
s.add_dependency(%q<xml-simple>.freeze, [">= 0"])
|
80
|
+
s.add_dependency(%q<bundler>.freeze, [">= 1.17"])
|
81
|
+
s.add_dependency(%q<flexmock>.freeze, ["~> 0.8.11"])
|
82
|
+
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
83
|
+
s.add_dependency(%q<rake>.freeze, [">= 0"])
|
84
|
+
s.add_dependency(%q<shoulda-context>.freeze, [">= 0"])
|
85
|
+
s.add_dependency(%q<test-unit>.freeze, [">= 0"])
|
86
|
+
s.add_dependency(%q<timecop>.freeze, [">= 0"])
|
87
|
+
end
|
88
|
+
else
|
89
|
+
s.add_dependency(%q<builder>.freeze, [">= 0"])
|
90
|
+
s.add_dependency(%q<mail>.freeze, ["> 2.2.5"])
|
91
|
+
s.add_dependency(%q<mime-types>.freeze, [">= 0"])
|
92
|
+
s.add_dependency(%q<xml-simple>.freeze, [">= 0"])
|
93
|
+
s.add_dependency(%q<bundler>.freeze, [">= 1.17"])
|
94
|
+
s.add_dependency(%q<flexmock>.freeze, ["~> 0.8.11"])
|
95
|
+
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
96
|
+
s.add_dependency(%q<rake>.freeze, [">= 0"])
|
97
|
+
s.add_dependency(%q<shoulda-context>.freeze, [">= 0"])
|
98
|
+
s.add_dependency(%q<test-unit>.freeze, [">= 0"])
|
99
|
+
s.add_dependency(%q<timecop>.freeze, [">= 0"])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# A quick little extension to use this lib with with rails 2.3.X
|
2
|
+
# To use it, in your environment.rb or some_environment.rb you simply set
|
3
|
+
#
|
4
|
+
# config.after_initialize do
|
5
|
+
# ActionMailer::Base.delivery_method = :amazon_ses
|
6
|
+
# ActionMailer::Base.custom_amazon_ses_mailer = AWS::SES::Base.new(:secret_access_key => S3_CONFIG[:secret_access_key], :access_key_id => S3_CONFIG[:access_key_id])
|
7
|
+
# end
|
8
|
+
|
9
|
+
module ActionMailer
|
10
|
+
class Base
|
11
|
+
cattr_accessor :custom_amazon_ses_mailer
|
12
|
+
|
13
|
+
def perform_delivery_amazon_ses(mail)
|
14
|
+
raise 'AWS::SES::Base has not been intitialized.' unless @@custom_amazon_ses_mailer
|
15
|
+
@@custom_amazon_ses_mailer.deliver!(mail)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/aws/ses.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
%w[ base64 cgi openssl digest/sha1 net/https net/http rexml/document time ostruct mail].each { |f| require f }
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'URI' unless defined? URI
|
5
|
+
rescue Exception => e
|
6
|
+
# nothing
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'xmlsimple' unless defined? XmlSimple
|
11
|
+
rescue Exception => e
|
12
|
+
require 'xml-simple' unless defined? XmlSimple
|
13
|
+
end
|
14
|
+
|
15
|
+
$:.unshift(File.dirname(__FILE__))
|
16
|
+
require 'ses/extensions'
|
17
|
+
|
18
|
+
require 'ses/response'
|
19
|
+
require 'ses/send_email'
|
20
|
+
require 'ses/info'
|
21
|
+
require 'ses/base'
|
22
|
+
require 'ses/version'
|
23
|
+
require 'ses/addresses'
|
24
|
+
|
25
|
+
if defined?(Rails)
|
26
|
+
major, minor = Rails.version.to_s.split('.')
|
27
|
+
require 'actionmailer/ses_extension' if major == '2' && minor == '3'
|
28
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module AWS
|
2
|
+
module SES
|
3
|
+
# AWS::SES::Addresses provides for:
|
4
|
+
# * Listing verified e-mail addresses
|
5
|
+
# * Adding new e-mail addresses to verify
|
6
|
+
# * Deleting verified e-mail addresses
|
7
|
+
#
|
8
|
+
# You can access these methods as follows:
|
9
|
+
#
|
10
|
+
# ses = AWS::SES::Base.new( ... connection info ... )
|
11
|
+
#
|
12
|
+
# # Get a list of verified addresses
|
13
|
+
# ses.addresses.list.result
|
14
|
+
#
|
15
|
+
# # Add a new e-mail address to verify
|
16
|
+
# ses.addresses.verify('jon@example.com')
|
17
|
+
#
|
18
|
+
# # Delete an e-mail address
|
19
|
+
# ses.addresses.delete('jon@example.com')
|
20
|
+
class Addresses < Base
|
21
|
+
def initialize(ses)
|
22
|
+
@ses = ses
|
23
|
+
end
|
24
|
+
|
25
|
+
# List all verified e-mail addresses
|
26
|
+
#
|
27
|
+
# Usage:
|
28
|
+
# ses.addresses.list.result
|
29
|
+
# =>
|
30
|
+
# ['email1@example.com', email2@example.com']
|
31
|
+
def list
|
32
|
+
@ses.request('ListVerifiedEmailAddresses')
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify(email)
|
36
|
+
@ses.request('VerifyEmailAddress',
|
37
|
+
'EmailAddress' => email
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(email)
|
42
|
+
@ses.request('DeleteVerifiedEmailAddress',
|
43
|
+
'EmailAddress' => email
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ListVerifiedEmailAddressesResponse < AWS::SES::Response
|
49
|
+
def result
|
50
|
+
if members = parsed['ListVerifiedEmailAddressesResult']['VerifiedEmailAddresses']
|
51
|
+
[members['member']].flatten
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
memoized :result
|
57
|
+
end
|
58
|
+
|
59
|
+
class VerifyEmailAddressResponse < AWS::SES::Response
|
60
|
+
end
|
61
|
+
|
62
|
+
class DeleteVerifiedEmailAddressResponse < AWS::SES::Response
|
63
|
+
def result
|
64
|
+
success?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Base
|
69
|
+
def addresses
|
70
|
+
@addresses ||= Addresses.new(self)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
data/lib/aws/ses/base.rb
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
module AWS #:nodoc:
|
2
|
+
# AWS::SES is a Ruby library for Amazon's Simple Email Service's REST API (http://aws.amazon.com/ses).
|
3
|
+
#
|
4
|
+
# == Getting started
|
5
|
+
#
|
6
|
+
# To get started you need to require 'aws/ses':
|
7
|
+
#
|
8
|
+
# % irb -rubygems
|
9
|
+
# irb(main):001:0> require 'aws/ses'
|
10
|
+
# # => true
|
11
|
+
#
|
12
|
+
# Before you can do anything, you must establish a connection using Base.new. A basic connection would look something like this:
|
13
|
+
#
|
14
|
+
# ses = AWS::SES::Base.new(
|
15
|
+
# :access_key_id => 'abc',
|
16
|
+
# :secret_access_key => '123'
|
17
|
+
# )
|
18
|
+
#
|
19
|
+
# The minimum connection options that you must specify are your access key id and your secret access key.
|
20
|
+
#
|
21
|
+
# === Connecting to a server from another region
|
22
|
+
#
|
23
|
+
# The default server API endpoint is "email.us-east-1.amazonaws.com", corresponding to the US East 1 region.
|
24
|
+
# To connect to a different one, just pass it as a parameter to the AWS::SES::Base initializer:
|
25
|
+
#
|
26
|
+
# ses = AWS::SES::Base.new(
|
27
|
+
# :access_key_id => 'abc',
|
28
|
+
# :secret_access_key => '123',
|
29
|
+
# :server => 'email.eu-west-1.amazonaws.com',
|
30
|
+
# :message_id_domain => 'eu-west-1.amazonses.com'
|
31
|
+
# )
|
32
|
+
#
|
33
|
+
|
34
|
+
module SES
|
35
|
+
|
36
|
+
API_VERSION = '2010-12-01'
|
37
|
+
|
38
|
+
DEFAULT_REGION = 'us-east-1'
|
39
|
+
|
40
|
+
SERVICE = 'ses'
|
41
|
+
|
42
|
+
DEFAULT_HOST = 'email.us-east-1.amazonaws.com'
|
43
|
+
|
44
|
+
DEFAULT_MESSAGE_ID_DOMAIN = 'email.amazonses.com'
|
45
|
+
|
46
|
+
USER_AGENT = 'github-aws-ses-ruby-gem'
|
47
|
+
|
48
|
+
DEFAULT_SIGNATURE_VERSION = 4
|
49
|
+
|
50
|
+
# Encodes the given string with the secret_access_key by taking the
|
51
|
+
# hmac-sha1 sum, and then base64 encoding it. Optionally, it will also
|
52
|
+
# url encode the result of that to protect the string if it's going to
|
53
|
+
# be used as a query string parameter.
|
54
|
+
#
|
55
|
+
# @param [String] secret_access_key the user's secret access key for signing.
|
56
|
+
# @param [String] str the string to be hashed and encoded.
|
57
|
+
# @param [Boolean] urlencode whether or not to url encode the result., true or false
|
58
|
+
# @return [String] the signed and encoded string.
|
59
|
+
def SES.encode(secret_access_key, str, urlencode=true)
|
60
|
+
digest = OpenSSL::Digest.new('sha256')
|
61
|
+
b64_hmac =
|
62
|
+
Base64.encode64(
|
63
|
+
OpenSSL::HMAC.digest(digest, secret_access_key, str)).gsub("\n","")
|
64
|
+
|
65
|
+
if urlencode
|
66
|
+
return CGI::escape(b64_hmac)
|
67
|
+
else
|
68
|
+
return b64_hmac
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Generates the HTTP Header String that Amazon looks for
|
73
|
+
#
|
74
|
+
# @param [String] key the AWS Access Key ID
|
75
|
+
# @param [String] alg the algorithm used for the signature
|
76
|
+
# @param [String] sig the signature itself
|
77
|
+
def SES.authorization_header(key, alg, sig)
|
78
|
+
"AWS3-HTTPS AWSAccessKeyId=#{key}, Algorithm=#{alg}, Signature=#{sig}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def SES.authorization_header_v4(credential, signed_headers, signature)
|
82
|
+
"AWS4-HMAC-SHA256 Credential=#{credential}, SignedHeaders=#{signed_headers}, Signature=#{signature}"
|
83
|
+
end
|
84
|
+
|
85
|
+
# AWS::SES::Base is the abstract super class of all classes who make requests against SES
|
86
|
+
class Base
|
87
|
+
include SendEmail
|
88
|
+
include Info
|
89
|
+
|
90
|
+
attr_reader :use_ssl, :server, :proxy_server, :port, :message_id_domain, :signature_version, :region,
|
91
|
+
:action, :action_time, :query
|
92
|
+
attr_accessor :settings
|
93
|
+
|
94
|
+
# @option options [String] :access_key_id ("") The user's AWS Access Key ID
|
95
|
+
# @option options [String] :secret_access_key ("") The user's AWS Secret Access Key
|
96
|
+
# @option options [Boolean] :use_ssl (true) Connect using SSL?
|
97
|
+
# @option options [String] :server ("email.us-east-1.amazonaws.com") The server API endpoint host
|
98
|
+
# @option options [String] :proxy_server (nil) An HTTP proxy server FQDN
|
99
|
+
# @option options [String] :user_agent ("github-aws-ses-ruby-gem") The HTTP User-Agent header value
|
100
|
+
# @option options [String] :region ("us-east-1") The server API endpoint host
|
101
|
+
# @option options [String] :message_id_domain ("us-east-1.amazonses.com") Domain used to build message_id header
|
102
|
+
# @return [Object] the object.
|
103
|
+
def initialize( options = {} )
|
104
|
+
|
105
|
+
options = { :access_key_id => "",
|
106
|
+
:secret_access_key => "",
|
107
|
+
:use_ssl => true,
|
108
|
+
:server => DEFAULT_HOST,
|
109
|
+
:message_id_domain => DEFAULT_MESSAGE_ID_DOMAIN,
|
110
|
+
:path => "/",
|
111
|
+
:user_agent => USER_AGENT,
|
112
|
+
:proxy_server => nil,
|
113
|
+
:region => DEFAULT_REGION,
|
114
|
+
:signature_version => DEFAULT_SIGNATURE_VERSION
|
115
|
+
}.merge(options)
|
116
|
+
|
117
|
+
@signature_version = options[:signature_version]
|
118
|
+
@server = options[:server]
|
119
|
+
@message_id_domain = options[:message_id_domain]
|
120
|
+
@proxy_server = options[:proxy_server]
|
121
|
+
@use_ssl = options[:use_ssl]
|
122
|
+
@path = options[:path]
|
123
|
+
@user_agent = options[:user_agent]
|
124
|
+
@region = options[:region]
|
125
|
+
@settings = {}
|
126
|
+
|
127
|
+
raise ArgumentError, "No :access_key_id provided" if options[:access_key_id].nil? || options[:access_key_id].empty?
|
128
|
+
raise ArgumentError, "No :secret_access_key provided" if options[:secret_access_key].nil? || options[:secret_access_key].empty?
|
129
|
+
raise ArgumentError, "No :use_ssl value provided" if options[:use_ssl].nil?
|
130
|
+
raise ArgumentError, "Invalid :use_ssl value provided, only 'true' or 'false' allowed" unless options[:use_ssl] == true || options[:use_ssl] == false
|
131
|
+
raise ArgumentError, "No :server provided" if options[:server].nil? || options[:server].empty?
|
132
|
+
raise ArgumentError, ":signature_version must be 2 or 4" unless [2, 4].include?(options[:signature_version])
|
133
|
+
|
134
|
+
if options[:port]
|
135
|
+
# user-specified port
|
136
|
+
@port = options[:port]
|
137
|
+
elsif @use_ssl
|
138
|
+
# https
|
139
|
+
@port = 443
|
140
|
+
else
|
141
|
+
# http
|
142
|
+
@port = 80
|
143
|
+
end
|
144
|
+
|
145
|
+
@access_key_id = options[:access_key_id]
|
146
|
+
@secret_access_key = options[:secret_access_key]
|
147
|
+
|
148
|
+
# Use proxy server if defined
|
149
|
+
# Based on patch by Mathias Dalheimer. 20070217
|
150
|
+
proxy = @proxy_server ? URI.parse(@proxy_server) : OpenStruct.new
|
151
|
+
@http = Net::HTTP::Proxy( proxy.host,
|
152
|
+
proxy.port,
|
153
|
+
proxy.user,
|
154
|
+
proxy.password).new(options[:server], @port)
|
155
|
+
|
156
|
+
@http.use_ssl = @use_ssl
|
157
|
+
end
|
158
|
+
|
159
|
+
def connection
|
160
|
+
@http
|
161
|
+
end
|
162
|
+
|
163
|
+
# Make the connection to AWS passing in our request.
|
164
|
+
# allow us to have a one line call in each method which will do all of the work
|
165
|
+
# in making the actual request to AWS.
|
166
|
+
def request(action, params = {})
|
167
|
+
@action = action
|
168
|
+
# Use a copy so that we don't modify the caller's Hash, remove any keys that have nil or empty values
|
169
|
+
params = params.reject { |_, value| value.nil? or value.empty?}
|
170
|
+
|
171
|
+
@action_time = Time.now.getutc
|
172
|
+
|
173
|
+
params.merge!( {"Action" => action,
|
174
|
+
"SignatureVersion" => signature_version.to_s,
|
175
|
+
"SignatureMethod" => 'HmacSHA256',
|
176
|
+
"AWSAccessKeyId" => @access_key_id,
|
177
|
+
"Version" => API_VERSION,
|
178
|
+
"Timestamp" => action_time.iso8601 } )
|
179
|
+
|
180
|
+
@query = params.sort.collect do |param|
|
181
|
+
CGI::escape(param[0]) + "=" + CGI::escape(param[1])
|
182
|
+
end.join("&")
|
183
|
+
response = connection.post(@path, query, get_req_headers)
|
184
|
+
|
185
|
+
response_class = AWS::SES.const_get( "#{action}Response" )
|
186
|
+
result = response_class.new(action, response)
|
187
|
+
|
188
|
+
if result.error?
|
189
|
+
raise ResponseError.new(result)
|
190
|
+
end
|
191
|
+
|
192
|
+
result
|
193
|
+
end
|
194
|
+
|
195
|
+
def get_req_headers
|
196
|
+
headers = {}
|
197
|
+
if signature_version == 4
|
198
|
+
headers['host'] = @server
|
199
|
+
headers['authorization'] = get_aws_auth_header_v4
|
200
|
+
headers['x-amz-date'] = amzdate
|
201
|
+
headers['user-agent'] = @user_agent
|
202
|
+
else
|
203
|
+
headers['x-amzn-authorization'] = get_aws_auth_header_v2
|
204
|
+
headers['date'] = action_time.httpdate
|
205
|
+
headers['user-agent'] = @user_agent
|
206
|
+
end
|
207
|
+
headers
|
208
|
+
end
|
209
|
+
|
210
|
+
# Set the Authorization header using AWS signed header authentication
|
211
|
+
def get_aws_auth_header_v2
|
212
|
+
encoded_canonical = SES.encode(@secret_access_key, httpdate, false)
|
213
|
+
SES.authorization_header(@access_key_id, 'HmacSHA256', encoded_canonical)
|
214
|
+
end
|
215
|
+
|
216
|
+
def get_aws_auth_header_v4
|
217
|
+
SES.authorization_header_v4(sig_v4_auth_credential, sig_v4_auth_signed_headers, sig_v4_auth_signature)
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
def sig_v4_auth_credential
|
223
|
+
@access_key_id + '/' + credential_scope
|
224
|
+
end
|
225
|
+
|
226
|
+
def sig_v4_auth_signed_headers
|
227
|
+
'host;x-amz-date'
|
228
|
+
end
|
229
|
+
|
230
|
+
def credential_scope
|
231
|
+
datestamp + '/' + region + '/' + SERVICE + '/' + 'aws4_request'
|
232
|
+
end
|
233
|
+
|
234
|
+
def string_to_sign
|
235
|
+
"AWS4-HMAC-SHA256\n" + amzdate + "\n" + credential_scope + "\n" + Digest::SHA256.hexdigest(canonical_request.encode('utf-8').b)
|
236
|
+
end
|
237
|
+
|
238
|
+
def amzdate
|
239
|
+
@action_time ||= Time.now.getutc
|
240
|
+
action_time.strftime('%Y%m%dT%H%M%SZ')
|
241
|
+
end
|
242
|
+
|
243
|
+
def datestamp
|
244
|
+
@action_time ||= Time.now.getutc
|
245
|
+
action_time.strftime('%Y%m%d')
|
246
|
+
end
|
247
|
+
|
248
|
+
def httpdate
|
249
|
+
@action_time ||= Time.now.getutc.httpdate
|
250
|
+
end
|
251
|
+
|
252
|
+
def canonical_request
|
253
|
+
"POST" + "\n" + "/" + "\n" + canonical_querystring + "\n" + canonical_headers + "\n" + sig_v4_auth_signed_headers + "\n" + payload_hash
|
254
|
+
end
|
255
|
+
|
256
|
+
def canonical_querystring
|
257
|
+
signature_version == 2 ? "Action=#{action}&Version=2013-10-15" : ''
|
258
|
+
end
|
259
|
+
|
260
|
+
def canonical_headers
|
261
|
+
'host:' + server + "\n" + 'x-amz-date:' + amzdate + "\n"
|
262
|
+
end
|
263
|
+
|
264
|
+
def payload_hash
|
265
|
+
Digest::SHA256.hexdigest(query.to_s.encode('utf-8'))
|
266
|
+
end
|
267
|
+
|
268
|
+
def sig_v4_auth_signature
|
269
|
+
OpenSSL::HMAC.hexdigest("SHA256", getSignatureKey, string_to_sign.encode('utf-8'))
|
270
|
+
end
|
271
|
+
|
272
|
+
def getSignatureKey
|
273
|
+
kDate = sign(('AWS4' + @secret_access_key).encode('utf-8'), datestamp)
|
274
|
+
kRegion = sign(kDate, region)
|
275
|
+
kService = sign(kRegion, SERVICE)
|
276
|
+
kSigning = sign(kService, 'aws4_request')
|
277
|
+
|
278
|
+
kSigning
|
279
|
+
end
|
280
|
+
|
281
|
+
def sign(key, msg)
|
282
|
+
OpenSSL::HMAC.digest("SHA256", key, msg.encode('utf-8'))
|
283
|
+
end
|
284
|
+
end # class Base
|
285
|
+
end # Module SES
|
286
|
+
end # Module AWS
|