aws-ses-v4 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|